package com.ck.ips.service.impl.program;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ck.common.model.*;
import com.ck.common.page.PageInfo;
import com.ck.core.constant.*;
import com.ck.core.constant.device.DeviceSettingType;
import com.ck.core.constant.program.*;
import com.ck.core.context.UserContextHolder;
import com.ck.core.util.BeanUtils;
import com.ck.core.util.DateUtils;
import com.ck.core.util.UUIDUtils;
import com.ck.ips.dao.program.ProgramMapper;
import com.ck.ips.model.vo.FileRealTimeDataVO;
import com.ck.ips.model.vo.program.*;
import com.ck.ips.service.DeviceService;
import com.ck.ips.service.program.*;
import com.ck.ips.service.report.ReportFormsService;
import com.ck.ips.service.sys.DictionaryService;
import com.ck.ips.service.sys.OperationLogService;
import com.ck.ips.service.sys.SysConfigService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.ParseException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author linqiang
 * @version v1.0
 * @Description 节目Service实现类
 * @date 2019-04-29 16:57
 */
@Service(value = "programService")
public class ProgramServiceImpl implements ProgramService {

    private Logger logger = LoggerFactory.getLogger(ProgramServiceImpl.class);

    @Autowired
    private ProgramMapper programMapper;
    @Autowired
    private JsonDataService jsonDataService;
    @Autowired
    private ScreenCanvasMappingService screenCanvasMappingService;
    @Autowired
    private ProgramSourceService programSourceService;
    @Autowired
    private ProgramDeviceService programDeviceService;
    @Autowired
    private SysConfigService sysConfigService;
    @Autowired
    private ProgramLogService programLogService;
    @Autowired
    private ProgramDownloadService programDownloadService;
    @Autowired
    private DeviceService deviceService;
    @Autowired
    private ProgramAddedLogService programAddedLogService;
    @Autowired
    private ProgramReducedLogService programReducedLogService;
    @Autowired
    private DictionaryService dictionaryService;
    @Autowired
    private MonitorDataFileService monitorDataFileService;
    @Autowired
    private ReportFormsService reportFormsService;
    @Autowired
    private OperationLogService operationLogService;

    @Override
    public OperationResult deleteByPrimaryKey(Long programId) {
        return null;
    }

    @Override
    public OperationResult insert(Program record) {
        return null;
    }

    @Override
    public Program selectByPrimaryKey(Long programId) {
        return null;
    }

    /**
     * 节目列表查询
     *
     * @param programName   节目名称
     * @param programStatus 节目状态
     * @param programOrigin 节目来源
     * @param deviceGroupId 投放区域（多个英文逗号隔开）
     * @param deviceType    终端类型（横屏，竖屏等）
     * @param pageNum       第几页
     * @param pageSize      每页条数
     * @param brandId       品牌id
     * @return
     */
    @Override
    public OperationResult listByPage(String programName, Integer programStatus, String programOrigin, String deviceGroupId,
                                      String deviceType, Integer pageNum, Integer pageSize, Integer brandId) {
        List<String> deviceGroupIdList = null;
        if (StringUtils.isNotBlank(deviceGroupId)) {
            deviceGroupIdList = Arrays.asList(deviceGroupId.split(SymbolConstant.COMMA_EN));
        }
        if (StringUtils.isNotBlank(programName) && programName.contains("_")) {
            //处理下划线_
            programName = programName.replaceAll("_", "\\\\_");
        }
        PageHelper.startPage(pageNum, pageSize);
        Page<ProgramListVO> page = programMapper.listByPage(programName, programStatus, brandId, programOrigin, deviceGroupIdList, deviceType, System.currentTimeMillis());
        if (null == page) {
            return OperationResult.newSuccessOperationResult(PageInfo.init());
        }
        return OperationResult.newSuccessOperationResult(PageInfo.convert(page));
    }

    /**
     * 素材JSON信息 （key为分辨率，value为素材信息）
     *
     * @param programVersion   节目版本号
     * @param programName      节目名称
     * @param deviceType       设备类型
     * @param deviceResolution 分辨率
     * @param putinLevel       投放级别
     * @param putinStartTime   投放开始时间
     * @param putinEndTime     投放结束时间
     * @param putinLimit       投放限制
     * @param sourceJson       素材JSON（key:分辨率， value:素材JSON数组）
     * @param deviceCodes      点位（设备序列号哦，多个英文逗号隔开）
     * @param checkPointer     是否检查点位（true检查， false不检查）
     * @param checkSource      是否检查素材（true检查， false不检查）
     * @param brandId          品牌id
     * @return OperationResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult saveBasicInfoAndPointerAndSource(String programName, String deviceType, String deviceResolution,
                                                            Integer putinLevel, String putinStartTime, String putinEndTime,
                                                            String putinLimit, String programVersion, String sourceJson,
                                                            String deviceCodes, Boolean checkPointer, Boolean checkSource, Long brandId) {
        //默认需要检查点位
        checkPointer = (checkPointer == null ? Boolean.TRUE : checkPointer);
        //默认需要检查素材
        checkSource = (checkSource == null ? Boolean.TRUE : checkSource);
        if (checkPointer && StringUtils.isBlank(deviceCodes)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.POINTER_NOT_NULL);
        }
        if (checkSource && StringUtils.isBlank(sourceJson)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.SOURCE_NOT_NULL);
        }
        //保存或更新基本信息
        OperationResult operationResult = saveProgramBaseInfo(programVersion, programName, deviceType, deviceResolution, putinLevel, putinStartTime, putinEndTime, putinLimit);
        if (!operationResult.isSucess()) {
            return operationResult;
        }
        Program program = (Program) operationResult.getData();
        OperationResult saveSourceResult = saveProgramSourceLocal(program, sourceJson, checkSource);
        if (!saveSourceResult.isSucess()) {
            return saveSourceResult;
        }
        String optMsg = "新增节目成功";
        if (program != null) {
            program.setBrandId(brandId);
        }
        if (null == program.getProgramId()) {
            programMapper.insert(program);
        } else {
            programMapper.updateByPrimaryKey(program);
            optMsg = "更新节目成功";
        }
        //保存操作日志
        programLogService.insertSuccessLog(programVersion, optMsg);
        //保存或更新点位信息
        OperationResult savePointerResult = batchInsertProgramDeviceCode(program.getProgramVersion(), deviceCodes, checkPointer);
        if (!savePointerResult.isSucess()) {
            return savePointerResult;
        }
        return OperationResult.newSuccessOperationResult("保存节目成功", program.getProgramVersion());
    }

    /**
     * 发布节目
     *
     * @param programVersion 节目版本号
     * @param deviceCodes    点位
     * @return OperationResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult publish(String programVersion, String deviceCodes) {
        if (StringUtils.isBlank(programVersion)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_VERSION_NOT_NULL);
        }

        if (StringUtils.isBlank(deviceCodes)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.POINTER_NOT_NULL);
        }

        // 检查节目各分辨率素材是否完整
        Program program = programMapper.selectByProgramVersion(programVersion);
        if (null == program) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NOT_EXISTS_NOT_NULL);
        }

        if (program.getPutinEndTime() < System.currentTimeMillis()) {
            //节目已过期
            return OperationResult.newValidateFailOperationResult("节目已过期，请修改投放时间");
        }

        // 校验素材是否完整，正确
        OperationResult operationResult = isProgramSourceOk(program.getProgramInfo(), program.getDeviceResolution());
        if (!operationResult.isSucess()) {
            return operationResult;
        }

        if (!ProgramStatusEnum.canPush(program.getProgramStatus())) {
            return OperationResult.newValidateFailOperationResult("节目已发布");
        }

        //保存或更新点位信息，节目操作日志
        OperationResult savePointerResult = batchInsertProgramDeviceCode(programVersion, deviceCodes, Boolean.TRUE);
        if (!savePointerResult.isSucess()) {
            return savePointerResult;
        }

        //修改节目状态，不需要审核则直接下发到终端
        String optMsg = publishOrAuthProgram(program);
        return OperationResult.newSuccessTipsOperationResult(optMsg);
    }


    /**
     * 审核通过节目
     *
     * @param programVersion 节目版本号
     * @return OperationResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult pass(String programVersion) {
        if (StringUtils.isBlank(programVersion)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_VERSION_NOT_NULL);
        }
        long currentTimeMillis = System.currentTimeMillis();
        //修改节目状态为‘待播放’
        Program program = programMapper.selectByProgramVersion(programVersion);
        if (null == program) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NOT_EXISTS_NOT_NULL);
        }
        program.setUpdateTime(System.currentTimeMillis());
        program.setProgramStatus(ProgramStatusEnum.TO_BE_PLAY.getValue());
        program.setUpdateUserName(UserContextHolder.currentUser().getUserName());
        program.setPushTime(currentTimeMillis);
        program.setRejectReson(null);
        programMapper.updateByPrimaryKey(program);

        //保存操作日志
        programLogService.insertSuccessLog(program.getProgramVersion(), "审核通过节目");

        List<String> programVersionList = new ArrayList<>();
        programVersionList.add(programVersion);
        //刷新缓存
        flushCacheByProgramVersionList(programVersionList, System.currentTimeMillis());

//        //异步打节目压缩包
//        createZip(program);

        return OperationResult.newSuccessTipsOperationResult("审核通过节目成功，节目ID：" + program.getProgramId());
    }

    /**
     * 审核驳回节目
     *
     * @param programVersion 节目版本号
     * @param rejectReason   驳回理由
     * @return OperationResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public synchronized OperationResult reject(String programVersion, String rejectReason) {
        if (StringUtils.isBlank(programVersion)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NOT_EXISTS_NOT_NULL);
        }
        if (StringUtils.isBlank(rejectReason)) {
            return OperationResult.newValidateFailOperationResult("驳回理由不能为空");
        }
        if (rejectReason.length() > NumberConstant.PROGRAM_REJECT_REASON_MAX_LENGTH) {
            return OperationResult.newValidateFailOperationResult("驳回理由不能超过30字");
        }
        //修改节目状态为‘待发布’
        Program program = programMapper.selectByProgramVersion(programVersion);
        if (null == program) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NOT_EXISTS_NOT_NULL);
        }
        program.setUpdateTime(System.currentTimeMillis());
        program.setProgramStatus(ProgramStatusEnum.TO_BE_PUBLISH.getValue());
        program.setRejectReson(rejectReason);
        program.setUpdateUserName(UserContextHolder.currentUser().getUserName());
        programMapper.updateByPrimaryKey(program);

        //保存操作日志
        programLogService.insertSuccessLog(program.getProgramVersion(), "审核驳回节目");
        return OperationResult.newSuccessTipsOperationResult("审核驳回节目成功，节目ID：" + program.getProgramId());
    }

    /**
     * 从终端发布节目
     *
     * @param programVersions 节目版本号（多个英文逗号隔开）
     * @param deviceCodes     设备序列号（多个英文逗号隔开）
     * @return OperationResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult fastPublishByProgramAndPointer(String programVersions, String deviceCodes) {
        if (StringUtils.isBlank(programVersions)) {
            return OperationResult.newValidateFailOperationResult("节目不能为空");
        }
        if (StringUtils.isBlank(deviceCodes)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.POINTER_NOT_NULL);
        }
        List<String> programVersionList = Arrays.asList(programVersions.split(SymbolConstant.COMMA_CN));

        List<Long> programIdList = new ArrayList<>();
        for (String programVersion : programVersionList) {
            // 检查节目各分辨率素材是否完整
            Program program = programMapper.selectByProgramVersion(programVersion);
            if (null == program) {
                return OperationResult.newValidateFailOperationResult("节目【" + programVersion + "】不存在或已被删除");
            }

            if (!ProgramStatusEnum.canPush(program.getProgramStatus())) {
                return OperationResult.newValidateFailOperationResult("节目【" + programVersion + "】已发布");
            }

            programIdList.add(program.getProgramId());
        }


        //保存或更新点位信息，节目操作日志
        OperationResult savePointerResult = batchInsertProgramDeviceCode(programVersions, deviceCodes, Boolean.TRUE);
        if (!savePointerResult.isSucess()) {
            return savePointerResult;
        }

        // 1.修改节目状态，添加节目操作日志
        // 2.根据配置若需要审核，则更改节目为‘待审核’，若不需要则更改节目为‘到待播放’直接下发到终端，刷新终端对应的缓存
        String msg = batchPublishOrAuthProgram(programVersionList, programIdList);

        return OperationResult.newSuccessTipsOperationResult(msg);
    }

    /**
     * 根据终端类型和分辨率筛选出可选的节目列表分页
     *
     * @param deviceCodes   设备序列号
     * @param programName   节目名称
     * @param origin        来源
     * @param deviceGroupId 终端组（区域），多个英文逗号隔开
     * @return
     */
    @Override
    public OperationResult getProgramByDevices(String deviceCodes, String programName, String origin, String deviceGroupId) {
        if (StringUtils.isBlank(deviceCodes)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.POINTER_NOT_NULL);
        }
        List<String> deviceCodeList = Arrays.asList(deviceCodes.split(SymbolConstant.COMMA_EN));

        List<String> deviceGroupIdList = null;
        if (StringUtils.isNotBlank(deviceGroupId)) {
            deviceGroupIdList = Arrays.asList(deviceGroupId.split(SymbolConstant.COMMA_EN));
        }

        //根据设备序列号获取设备类型和分辨率, 由于横屏竖屏目前难以界定，暂只根据分辨率做筛选
        //排序前的分辨率：如1920x1080,1080x1920
        List<String> resolutionList = deviceService.getDistinctResolvingPowerByDeviceCodes(deviceCodeList, deviceGroupIdList);
        //排序处理
        Collections.sort(resolutionList);
        //排序后重新拼接成英文逗号隔开的字符串
        String resolutions = Joiner.on(SymbolConstant.COMMA_EN).join(resolutionList);
        if (StringUtils.isBlank(resolutions)) {
            return OperationResult.newValidateFailOperationResult("所选点位分辨率不能为空");
        }

        List<ProgramListVO> programListVOS = programMapper.filterProgramWithDevicesByPage(null, resolutions, programName, origin, null, System.currentTimeMillis());
        return OperationResult.newSuccessOperationResult(programListVOS);
    }

    /**
     * 获取节目详情
     *
     * @param customerName   客户名称
     * @param programVersion 节目版本号
     * @return OperationResult
     */
    @Override
    public OperationResult getDetail(String customerName, String programVersion) {
        if (StringUtils.isBlank(programVersion)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_VERSION_NOT_NULL);
        }

        //判断节目是否存在
        ProgramDetailVO programDetailVO = programMapper.getDetail(customerName, programVersion, System.currentTimeMillis());
        if (null == programDetailVO) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NOT_EXISTS_NOT_NULL);
        }

        //获取素材JSON信息，若为空则表示不完整
        String sourceInfo = getProgramSourceJsonInfo(programDetailVO.getProgramInfo(), programDetailVO.getDeviceResolution());
        programDetailVO.setSourceJson(sourceInfo);

        //获取素材列表
        List<ProgramSource> programSourceList = programSourceService.listByProgramVersion(programVersion);
        List<ProgramDetailSourceVO> programSourceVOList = convertProgramSource(programSourceList);
        programDetailVO.setSourceList(programSourceVOList);

        //投放终端详情列表
        List<ProgramDeviceBasicInfoVO> programDeviceVOList = programDeviceService.listDeviceBasicInfoByProgramVersion(programVersion);

        //统计终端数量，终端组数量
        ProgramStatisticsInfoVO countVO = programDeviceService.countDeviceAndGroupByProgramVersion(programVersion);

        programDetailVO.setDeviceCount(countVO.getDeviceCount());
        programDetailVO.setDeviceGroupCount(countVO.getDeviceGroupCount());
        programDetailVO.setDeviceList(programDeviceVOList);

        //设置画布
        String resolutions = programDetailVO.getDeviceResolution();
        if (StringUtils.isNotBlank(resolutions)) {
            JSONObject screenCanvasMappingObj = new JSONObject();
            String[] resolutionArr = resolutions.split(SymbolConstant.COMMA_EN);
            for (String resolution : resolutionArr) {
                String[] resolutionItemArr = resolution.split(SymbolConstant.X_EN);
                ScreenCanvasMapping screenCanvasMapping = screenCanvasMappingService.selectByScreen(Integer.parseInt(resolutionItemArr[0]), Integer.parseInt(resolutionItemArr[1]));
                if (null != screenCanvasMapping) {
                    screenCanvasMappingObj.put(resolution, screenCanvasMapping.getCanvasWidth() + SymbolConstant.X_EN + screenCanvasMapping.getCanvasHeight());
                }
            }
            programDetailVO.setScreenCanvasMappingJson(screenCanvasMappingObj.toJSONString());
        }

        return OperationResult.newSuccessOperationResult(programDetailVO);
    }

    /**
     * 转换
     *
     * @param programSourceList 素材列表
     * @return
     */
    private List<ProgramDetailSourceVO> convertProgramSource(List<ProgramSource> programSourceList) {
        if (CollectionUtils.isEmpty(programSourceList)) {
            return new ArrayList<>();
        }
        return programSourceList.stream().map((ProgramSource item) ->
                new ProgramDetailSourceVO(item.getProgramVersion(), item.getUrl(), item.getMd5(), item.getFileName(),
                        item.getFileType(), item.getDuration(), item.getDeviceResolution())).collect(Collectors.toList());
    }

    /**
     * 获取节目投放区域，下发详情，下载详情列表分页
     *
     * @param programVersion 节目版本号
     * @param deviceName     设备名称
     * @param deviceCode     设备序列号
     * @param pageNum        当前页
     * @param pageSize       每页条数
     * @return OperationResult
     */
    @Override
    public OperationResult getDevicesByPage(String programVersion, String deviceName, String deviceCode, Integer pageNum, Integer pageSize) {
        if (StringUtils.isBlank(programVersion)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_VERSION_NOT_NULL);
        }
        pageNum = (pageNum == null ? PageConstant.DEFAULT_PAGE_NUM : pageNum);
        pageSize = (pageSize == null ? PageConstant.DEFAULT_PAGE_SIZE : pageSize);
        PageHelper.startPage(pageNum, pageSize);
        Page<ProgramDeviceDetailVO> programDeviceDetailVOPage = programMapper.getDevicesByPage(programVersion, deviceName, deviceCode, System.currentTimeMillis());
        return OperationResult.newSuccessOperationResult(PageInfo.convert(programDeviceDetailVOPage));
    }

    /**
     * 获取节目统计信息
     * 统计投放终端组数量，终端数量，素材数量，投放时间，
     * 下发情况（已下发成功：X个终端；发送中：X个终端；发送失败：X个终端）
     * 下载情况（已下载成功：X个终端；下载中：X个终端；下载失败：X个终端）
     *
     * @param programVersion 节目版本号
     * @return OperationResult
     */
    @Override
    public OperationResult getStatisticsInfo(String programVersion) {
        if (StringUtils.isBlank(programVersion)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_VERSION_NOT_NULL);
        }
        ProgramStatisticsInfoVO programStatisticsInfoVO = new ProgramStatisticsInfoVO();
        //统计投放终端组数量，终端数量，素材数量，投放时间
        ProgramStatisticsInfoVO baseStatisticsInfo = programMapper.getBaseStatisticsInfo(programVersion, System.currentTimeMillis());
        //下发情况,下载情况
        ProgramStatisticsInfoVO pushAndDownloadStatisticsInfo = programMapper.getPushAndDownloadStatisticsInfo(programVersion, System.currentTimeMillis());
        if (null != baseStatisticsInfo) {
            programStatisticsInfoVO.setDeviceCount(baseStatisticsInfo.getDeviceCount());
            programStatisticsInfoVO.setDeviceGroupCount(baseStatisticsInfo.getDeviceGroupCount());
            programStatisticsInfoVO.setPutinEndTime(baseStatisticsInfo.getPutinEndTime());
            programStatisticsInfoVO.setPutinStartTime(baseStatisticsInfo.getPutinStartTime());
            programStatisticsInfoVO.setSourceCount(baseStatisticsInfo.getSourceCount());
        }
        if (null != pushAndDownloadStatisticsInfo) {
            programStatisticsInfoVO.setPushFailCount(pushAndDownloadStatisticsInfo.getPushFailCount());
            programStatisticsInfoVO.setPushingCount(pushAndDownloadStatisticsInfo.getPushingCount());
            programStatisticsInfoVO.setPushSuccessCount(pushAndDownloadStatisticsInfo.getPushSuccessCount());
            programStatisticsInfoVO.setDownloadFailCount(pushAndDownloadStatisticsInfo.getDownloadFailCount());
            programStatisticsInfoVO.setDownloadingCount(pushAndDownloadStatisticsInfo.getDownloadingCount());
            programStatisticsInfoVO.setDownloadSuccessCount(pushAndDownloadStatisticsInfo.getDownloadSuccessCount());
            programStatisticsInfoVO.setNotStartDownloadCount(pushAndDownloadStatisticsInfo.getNotStartDownloadCount());
        }
        return OperationResult.newSuccessOperationResult(programStatisticsInfoVO);
    }

    /**
     * 增播
     * 1.只能选择一个终端
     * 2.直接发布
     *
     * @param deviceType       设备类型
     * @param deviceResolution 设备分辨率
     * @param deviceCode       设备序列号
     * @param programName      节目名称
     * @param putinLevel       投放级别
     * @param putinLimit       投放限制
     * @param putinStartTime   投放开始时间
     * @param putinEndTime     投放结束时间
     * @param json             素材JSON信息 （key为分辨率，value为素材信息）
     * @param brandId          品牌id
     * @return OperationResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult added(String deviceType, String deviceResolution, String deviceCode, String programName,
                                 Integer putinLevel, String putinLimit, String putinStartTime, String putinEndTime, String json, Long brandId) {
        if (StringUtils.isBlank(deviceCode)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.DEVICE_NOT_NULL);
        }
        //检查节目基本信息
        OperationResult checkParamResult = checkProgramBasicInfoParams(Boolean.TRUE, deviceResolution, programName, putinLevel, putinLimit, putinStartTime, putinEndTime);
        if (!checkParamResult.isSucess()) {
            return checkParamResult;
        }

        if (StringUtils.isBlank(json)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.SOURCE_NOT_NULL);
        }
        //检测节目名称唯一性
        boolean isExists = isProgramNameExists(null, programName);
        if (isExists) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NAME_EXISTS);
        }

        //校验素材
        JSONObject jsonObject = JSONObject.parseObject(json);
        JSONArray sources = jsonObject.getJSONArray(deviceResolution);
        if (CollectionUtils.isEmpty(sources)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.SOURCE_NOT_NULL);
        }

        //当前时间戳
        long currentTimeMills = System.currentTimeMillis();
        //增播节目
        String programType = ProgramTypeEnum.ADD.getValue();

        //处理投放时间
        long startTime = 0;
        long endTime = 0;
        try {
            startTime = DateUtils.stringTimeToLongTimeStamp(putinStartTime, DateUtils.PATTEN_YMD_HMS);
            endTime = DateUtils.stringTimeToLongTimeStamp(putinEndTime, DateUtils.PATTEN_YMD_HMS);
        } catch (ParseException e) {
            return OperationResult.newValidateFailOperationResult("投放时间格式错误");
        }
        if (startTime >= endTime) {
            return OperationResult.newValidateFailOperationResult("投放开始时间不能大于等于结束时间");
        }

        //添加节目
        Program program = new Program();
        program.setProgramVersion(UUIDUtils.getUUIDString());
        //增播节目状态初始为‘待播放’
        program.setProgramStatus(ProgramStatusEnum.TO_BE_PLAY.getValue());
        program.setPutinStartTime(startTime);
        program.setPutinEndTime(endTime);
        program.setPutinLevel(putinLevel);
        program.setPutinLimit(putinLimit);
        program.setOrigin(ProgramOriginEnum.SELF.getValue());
        program.setProgramName(programName);
        program.setDeviceResolution(deviceResolution);
        program.setDeviceType(deviceType);
        program.setAddTime(currentTimeMills);
        program.setAddUserName(UserContextHolder.currentUser().getUserName());
        program.setProgramType(programType);
        program.setIsDelete(Boolean.FALSE);
        program.setBrandId(brandId);
        programMapper.insert(program);

        //保存并校验节目素材信息
        OperationResult operationResult = saveProgramSourceLocal(program, json, Boolean.TRUE);
        if (!operationResult.isSucess()) {
            return operationResult;
        }
        programMapper.updateByPrimaryKey(program);

        //添加节目操作日志
        programLogService.insertSuccessLog(program.getProgramVersion(), "增播节目");

        //添加节目设备关联
        ProgramDevice programDevice = getProgramDevice(currentTimeMills, program.getProgramVersion(), deviceCode);
        programDeviceService.insert(programDevice);

        //添加节目下载
        ProgramDownload programDownload = getProgramDownload(program.getProgramVersion(), deviceCode);
        programDownloadService.insert(programDownload);

        //添加增播日志
        ProgramAddedLog programAddedLog = new ProgramAddedLog();
        programAddedLog.setAddTime(currentTimeMills);
        programAddedLog.setDeviceCode(deviceCode);
        programAddedLog.setProgramVersion(program.getProgramVersion());
        programAddedLog.setAddUserName(UserContextHolder.currentUser().getUserName());
        programAddedLog.setAddUserRealname(UserContextHolder.currentUser().getRealName());
        programAddedLog.setIsDelete(Boolean.FALSE);
        programAddedLogService.insert(programAddedLog);

        //发布或审核通过节目，不需要审核则直接下发到终端
        publishOrAuthProgram(program);

        return OperationResult.newSuccessOperationResult("增播，节目名称：" + programName, program.getProgramVersion());
    }


    /**
     * 减播
     * 只能选择一个终端， 减播的素材只是在终端不播放，后台节目中素材不变，因为可能多个终端在播放
     *
     * @param deviceCode 设备序列号
     * @param json       需要减播的素材数组json字符串（属性包括：节目版本号，素材名称，素材url）
     * @return OperationResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult reduce(String deviceCode, String json) {
        if (StringUtils.isBlank(deviceCode)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.DEVICE_NOT_NULL);
        }
        if (StringUtils.isBlank(json)) {
            return OperationResult.newValidateFailOperationResult("减播素材不能为空");
        }
        //检查素材
        List<ProgramReducedSourceVO> sourceList = JSONObject.parseArray(json, ProgramReducedSourceVO.class);
        if (CollectionUtils.isEmpty(sourceList)) {
            return OperationResult.newValidateFailOperationResult("减播素材不能为空");
        }

        long currentTimeMillis = System.currentTimeMillis();

        List<String> sourceNameList = new ArrayList<>();
        List<String> programVersionList = new ArrayList<>();

        //转换ProgramReducedSourceVO -> ProgramReducedLog
        List<ProgramReducedLog> programReducedLogList = sourceList.stream().map(item -> {
            sourceNameList.add(item.getFileName());
            if (!programVersionList.contains(item.getProgramVersion())) {
                programVersionList.add(item.getProgramVersion());
            }
            ProgramReducedLog programReducedLog = new ProgramReducedLog();
            programReducedLog.setAddTime(currentTimeMillis);
            programReducedLog.setDeviceCode(deviceCode);
            programReducedLog.setProgramVersion(item.getProgramVersion());
            programReducedLog.setSourceName(item.getFileName());
            programReducedLog.setSourceUrl(item.getUrl());
            programReducedLog.setAddUserName(UserContextHolder.currentUser().getUserName());
            programReducedLog.setAddUserRealname(UserContextHolder.currentUser().getRealName());
            programReducedLog.setIsDelete(Boolean.FALSE);
            return programReducedLog;
        }).collect(Collectors.toList());

        //添加减播日志
        programReducedLogService.batchInsert(programReducedLogList);

        //减播素材所属节目下载表状态更改为‘1：未开始’
        programDownloadService.batchUpdateStatus(programVersionList, deviceCode, ProgramDownloadStatusEnum.NOT_START.getValue());

        //刷新缓存，下发到终端有减播
        List<String> deviceCodeList = new ArrayList<>();
        deviceCodeList.add(deviceCode);
        flushCacheByDeviceCodeList(deviceCodeList, currentTimeMillis);
        return OperationResult.newSuccessTipsOperationResult("减播，素材：" + Joiner.on(SymbolConstant.COMMA_EN).join(sourceNameList));
    }

    /**
     * 根据终端获取素材列表
     * 1.等待播放，当前播放的素材
     * 2.包括素材累计播放次数，节目投放信息等
     * 3.支持一下查询：终端详情中播放列表，终端列表中播放列表，文件减播时选择素材列表
     *
     * @param deviceCode 设备序列号（必填）
     * @param sourceName 素材名称（选填）
     * @return OperationResult
     */
    @Override
    public OperationResult getPlayListByDeviceCode(String deviceCode, String sourceName) {
        if (StringUtils.isBlank(deviceCode)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.DEVICE_NOT_NULL);
        }
        // TODO 等待素材播放次数统计接口
        List<ProgramPlayListVO> programPlayListVOList = programMapper.getNewestSourcesByDeviceCode(deviceCode, null, null, null);
        return OperationResult.newSuccessOperationResult(programPlayListVOList);
    }


    /**
     * 节目下线
     * 1.将节目状态改为‘待发布’重新编辑
     * 2.删除 programDownload 相关数据
     * 3.刷新缓存告知终端下线该节目
     *
     * @param programVersion 节目版本号
     * @return OperationResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult offline(String programVersion) {
        if (StringUtils.isBlank(programVersion)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_VERSION_NOT_NULL);
        }

        long currentTimeMillis = System.currentTimeMillis();

        List<String> programVersionList = new ArrayList<>();
        programVersionList.add(programVersion);

        //添加节目操作日志
        programLogService.insertSuccessLog(programVersion, "节目下线");

        //物理删除节目下载
        programDownloadService.batchDelete(programVersionList);

        // 逻辑删除节目的减播记录
        programReducedLogService.batchDelete(programVersionList);

        //刷新缓存，通知终端下线节目
        flushCacheByProgramVersionList(programVersionList, currentTimeMillis);

        //修改节目为‘待发布’
        List<Program> programForUpdate = getProgramForUpdate(programVersionList, ProgramStatusEnum.TO_BE_PUBLISH.getValue(),
                UserContextHolder.currentUser().getUserName(), currentTimeMillis, null);
        programMapper.batchUpdateSelective(programForUpdate);
        return OperationResult.newSuccessTipsOperationResult("下线成功");
    }

    /**
     * api新增节目
     * 素材JSON格式：
     *
     * @param customerName   客户名称
     * @param deviceCodes    设备序列号（多个已英文逗号分割）
     * @param programName    节目名称
     * @param putinLevel     投放级别
     * @param putinLimit     投放限制
     * @param putinStartTime 投放开始时间
     * @param putinEndTime   投放结束时间
     * @param sources        素材JSON信息 （key为分辨率，value为素材信息）
     * @return OperationResult
     * @see static/template/api_program_source_json_demo.json
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult apiAdd(String customerName, String deviceCodes, String programName, Integer putinLevel, String putinLimit,
                                  String putinStartTime, String putinEndTime, String sources) {
        if (StringUtils.isBlank(customerName)) {
            return OperationResult.newValidateFailOperationResult("客户名称不能为空");
        }
        if (StringUtils.isBlank(deviceCodes)) {
            return OperationResult.newValidateFailOperationResult("设备不能为空");
        }

        //检查节目基本信息
        OperationResult checkParamResult = checkProgramBasicInfoParams(Boolean.FALSE, null, programName, putinLevel, putinLimit, putinStartTime, putinEndTime);
        if (!checkParamResult.isSucess()) {
            return checkParamResult;
        }

        if (StringUtils.isBlank(sources)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.SOURCE_NOT_NULL);
        }

        //检测节目名称唯一性
        boolean isExists = isProgramNameExists(null, programName);
        if (isExists) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NAME_EXISTS);
        }

        //校验参数投放级别
        boolean isPutinLevelExists = dictionaryService.isExists(DictionaryTypeEnum.PROGRAM_PUTIN_LEVEL.getType(), String.valueOf(putinLevel));
        if (!isPutinLevelExists) {
            return OperationResult.newValidateFailOperationResult("投放级别错误");
        }

        //校验参数投放限制
        boolean isPutinLimitExists = dictionaryService.isExists(DictionaryTypeEnum.PROGRAM_PUTIN_LIMIT.getType(), putinLimit);
        if (!isPutinLimitExists) {
            return OperationResult.newValidateFailOperationResult("投放限制错误");
        }

        //当前时间戳
        long currentTimeMills = System.currentTimeMillis();

        //处理投放时间
        long startTime = 0;
        long endTime = 0;
        try {
            startTime = DateUtils.stringTimeToLongTimeStamp(putinStartTime, DateUtils.PATTEN_YMD_HMS);
            endTime = DateUtils.stringTimeToLongTimeStamp(putinEndTime, DateUtils.PATTEN_YMD_HMS);
        } catch (ParseException e) {
            return OperationResult.newValidateFailOperationResult("投放时间格式错误，请参照格式：yyyy-MM-dd HH:mm:ss");
        }
        if (startTime >= endTime) {
            return OperationResult.newValidateFailOperationResult("投放开始时间必须小于结束时间");
        }
        if (currentTimeMills >= endTime) {
            return OperationResult.newValidateFailOperationResult("投放结束时间必须大于当前时间");
        }

        //校验素材
        JSONObject sourcesObj = JSONObject.parseObject(sources);
        if (null == sourcesObj) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.SOURCE_CONTENT_NOT_NULL);
        }
        Set<String> resolutionSet = sourcesObj.keySet();
        if (CollectionUtils.isEmpty(resolutionSet)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.SOURCE_CONTENT_NOT_NULL);
        }

        List<String> resolutionList = new ArrayList<>(resolutionSet);

        //将设备序列号转list,根据设备序列号查询设备分辨率和设备类型
        //由于横屏竖屏目前难以界定，暂只根据分辨率做筛选
        List<String> deviceCodeList = Arrays.asList(deviceCodes.split(SymbolConstant.COMMA_EN));
        List<String> deviceResolutionInDB = deviceService.getDistinctResolvingPowerByDeviceCodes(deviceCodeList, null);

        //判断分辨率是否完整
        for (String resolution : deviceResolutionInDB) {
            if (!resolutionList.contains(resolution)) {
                return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】素材不能为空");
            }
        }

        //排序处理, 排序后重新拼接成英文逗号隔开的字符串
        Collections.sort(resolutionList);
        String resolutions = Joiner.on(SymbolConstant.COMMA_EN).join(resolutionList);

        //初始化节目属性
        Program program = new Program();
        program.setProgramVersion(UUIDUtils.getUUIDString());
        //API节目状态初始为‘待播放’
        program.setProgramStatus(ProgramStatusEnum.TO_BE_PLAY.getValue());
        program.setPutinStartTime(startTime);
        program.setPutinEndTime(endTime);
        program.setPutinLevel(putinLevel);
        program.setPutinLimit(putinLimit);
        program.setOrigin(ProgramOriginEnum.API.getValue());
        program.setProgramName(programName);
        program.setDeviceResolution(resolutions);
        // 获取设备类型，此处节目的设备类型无意义，终端是根据分辨率获取节目，因此暂不在列表显示，
        program.setDeviceType(null);
        program.setAddTime(currentTimeMills);
        program.setAddUserName(customerName);
        // API新增为普通节目
        program.setProgramType(ProgramTypeEnum.NORMAL.getValue());
        program.setIsDelete(Boolean.FALSE);
        programMapper.insert(program);

        //校验节目素材信息，保存节目
        OperationResult operationResult = saveProgramSourceLocal(program, sources, Boolean.TRUE);
        if (!operationResult.isSucess()) {
            return operationResult;
        }
        programMapper.updateByPrimaryKey(program);

        //添加节目操作日志
        programLogService.insertSuccessLog(program.getProgramVersion(), customerName, "API新增节目并下发到终端");

        //添加节目设备关联
        batchInsertProgramDeviceCode(program.getProgramVersion(), deviceCodes, Boolean.TRUE);

        //发布或审核通过节目，不需要审核则直接下发到终端
        publishOrAuthProgram(program);

        return OperationResult.newSuccessOperationResult("API新增节目成功，节目ID：" + program.getProgramId(), program.getProgramVersion());
    }

    /**
     * 获取待播放和播放中的节目列表
     *
     * @return List
     */
    @Override
    public List<Program> getToPlayAndPlayingPrograms() {
        return programMapper.getToPlayAndPlayingPrograms();
    }

    /**
     * 根据节目版本号统计设备数
     *
     * @param programVersion 节目版本号
     * @return 条数
     */
    @Override
    public int countDeviceProgramByProgramVersion(String programVersion) {
        return programMapper.countDeviceProgramByProgramVersion(programVersion);
    }

    /**
     * 批量更新节目状态
     *
     * @param newStatus          新状态
     * @param programVersionList 节目版本号集合
     * @param updateTime         更新时间
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchUpdateStatus(ProgramStatusEnum newStatus, List<String> programVersionList, Long updateTime) {
        //批量修改节目状态
        List<Program> programList = getProgramForUpdate(programVersionList, newStatus.getValue(), UserContextHolder.currentUser().getUserName(), updateTime, null);
        programMapper.batchUpdateSelective(programList);
        programLogService.batchInsertSuccessLog(programVersionList, "修改状态为：" + newStatus.getName());
    }


    /**
     * 终端节目时删除节目相关
     *
     * @param deviceCodeList 设备序列号
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchDeleteByDeviceCodeForDeviceUnit(List<String> deviceCodeList) {
        //删除节目与设备关联
        programDeviceService.batchDelete(null, deviceCodeList);

        //刷新终端缓存，停止播放节目
        flushCacheByDeviceCodeList(deviceCodeList, System.currentTimeMillis());
    }

    /**
     * 根据终端类型和分辨率筛选出可选的节目所投放的终端
     *
     * @param deviceCodes 设备序列号（多个英文逗号隔开）
     * @return OperationResult
     */
    @Override
    public OperationResult getDeviceSelect(String deviceCodes) {
        if (StringUtils.isBlank(deviceCodes)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.POINTER_NOT_NULL);
        }
        List<String> deviceCodeList = Arrays.asList(deviceCodes.split(SymbolConstant.COMMA_EN));

        //根据设备序列号获取设备类型和分辨率, 由于横屏竖屏目前难以界定，暂只根据分辨率做筛选
        //排序前的分辨率：如1920x1080,1080x1920
        List<String> resolutionList = deviceService.getDistinctResolvingPowerByDeviceCodes(deviceCodeList, null);
        //排序处理
        Collections.sort(resolutionList);
        //排序后重新拼接成英文逗号隔开的字符串
        String resolutions = Joiner.on(SymbolConstant.COMMA_EN).join(resolutionList);
        if (StringUtils.isBlank(resolutions)) {
            return OperationResult.newValidateFailOperationResult("所选点位分辨率不能为空");
        }
        List<String> deviceCodeSelectList = programMapper.listProgramPutinDeviceCodesBySelectDeviceCodes(resolutions);
        return OperationResult.newSuccessOperationResult(deviceCodeSelectList);
    }

    /**
     * 查询列表
     *
     * @param customerName API客户名称
     * @param pageNum
     * @param pageSize
     * @return OperationResult
     */
    @Override
    public OperationResult list(String customerName, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        Page<ProgramListVO> page = programMapper.list(customerName, System.currentTimeMillis());
        return OperationResult.newSuccessOperationResult(PageInfo.convert(page));
    }

    /**
     * 删除节目
     *
     * @param programVersion 节目版本号
     * @return OperationResult
     */
    @Override
    public OperationResult delete(String programVersion) {
        Program program = programMapper.selectByProgramVersion(programVersion);
        if (null == program) {
            return OperationResult.newValidateFailOperationResult("节目已删除");
        }
        long programId = program.getProgramId();

        //是否需要审核
        Sysconfig sysconfig = sysConfigService.getByType(SysConfigNameEnum.NEED_AUTH_PROGRAM);
        if (null == sysconfig) {
            return OperationResult.newSystemErrorOperationResult();
        }
        boolean needAuth = Boolean.parseBoolean(sysconfig.getValue());

        //判断是否可以删除
        ProgramDetailVO detail = programMapper.getDetail(null, programVersion, System.currentTimeMillis());
        if (!canDelete(detail, needAuth)) {
            return OperationResult.newValidateFailOperationResult("状态为‘" + detail.getProgramStatusName() + "’的节目不能被删除");
        }

        List<String> programVersionList = new ArrayList<>();
        programVersionList.add(programVersion);

        // 删除节目相关
        programMapper.deleteByProgramVersion(programVersion);
        programDeviceService.batchDelete(programVersionList, null);
        programSourceService.batchDelete(programVersion);
        programDownloadService.batchDelete(programVersionList);
        return OperationResult.newSuccessTipsOperationResult("删除成功，节目ID：" + programId);
    }

    /**
     * 复制节目
     *
     * @param programVersion 节目版本号
     * @return OperationResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult copy(String programVersion) {
        if (StringUtils.isBlank(programVersion)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_VERSION_NOT_NULL);
        }

        //新的节目版本号
        String newProgramVersion = UUIDUtils.getUUIDString();
        long currentTimeMillis = System.currentTimeMillis();

        //原始节目
        Program oldProgram = programMapper.selectByProgramVersion(programVersion);
        if (null == oldProgram) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NOT_EXISTS_NOT_NULL);
        }
        //原始节目素材
        List<ProgramSource> programSourceList = programSourceService.listByProgramVersion(programVersion);
        //原始节目点位
        List<ProgramDevice> programDeviceList = programDeviceService.list(oldProgram.getProgramVersion());

        //构造复制的节目
        Program copyProgram = new Program();
        BeanUtils.copyBeanToBean(oldProgram, copyProgram);

        copyProgram.setRejectReson(null);
        copyProgram.setPushTime(null);
        copyProgram.setUpdateUserName(null);
        copyProgram.setUpdateTime(null);
        copyProgram.setProgramId(null);
        copyProgram.setIsDelete(Boolean.FALSE);
        copyProgram.setProgramVersion(newProgramVersion);
        //复制的节目为‘待发布’状态
        copyProgram.setProgramStatus(ProgramStatusEnum.TO_BE_PUBLISH.getValue());
        //添加人为当前用户
        copyProgram.setAddUserName(UserContextHolder.currentUser().getUserName());
        copyProgram.setAddTime(currentTimeMillis);
        //节目名称为：原始名称+“-copy”
        copyProgram.setProgramName(oldProgram.getProgramName() + ProgramConstant.COPY_PROGRAM_NAME_SUFFIX);

        //重构节目素材json
        JSONObject programInfoObjCopy = new JSONObject();
        String oldProgramInfo = copyProgram.getProgramInfo();
        JSONObject oldProgramInfoObj = JSONObject.parseObject(oldProgramInfo);
        for (String resolution : oldProgramInfoObj.keySet()) {
            JSONObject jsonObject = oldProgramInfoObj.getJSONObject(resolution);
            if (null != jsonObject) {
                JSONObject jsonObjectCopy = new JSONObject();
                Long jsonId = jsonObject.getLong(JsonKeyEnum.JSON_ID.getValue());
                JsonData jsonData = jsonDataService.selectByPrimaryKey(jsonId);
                if (null != jsonData) {
                    JsonData jsonDataCopy = new JsonData();
                    jsonDataCopy.setJsonContent(jsonData.getJsonContent());
                    jsonDataCopy.setAddTime(currentTimeMillis);
                    jsonDataService.insert(jsonDataCopy);
                    jsonObjectCopy.put(JsonKeyEnum.JSON_ID.getValue(), jsonDataCopy.getJsonId());
                }
                programInfoObjCopy.put(resolution, jsonObjectCopy);
            }
        }
        copyProgram.setProgramInfo(programInfoObjCopy.toJSONString());
        programMapper.insert(copyProgram);
        programLogService.insertSuccessLog(newProgramVersion, "新增节目（复制），原始节目ID：" + oldProgram.getProgramId());

        //新节目素材
        if (CollectionUtils.isNotEmpty(programSourceList)) {
            List<ProgramSource> programSourceListCopy = programSourceList.stream().map(programSource -> {
                ProgramSource programSourceCopy = new ProgramSource();
                BeanUtils.copyBeanToBean(programSource, programSourceCopy);
                programSourceCopy.setProgramSourceId(null);
                programSourceCopy.setAddTime(currentTimeMillis);
                programSourceCopy.setUpdateTime(null);
                programSourceCopy.setProgramVersion(newProgramVersion);
                return programSourceCopy;
            }).collect(Collectors.toList());
            programSourceService.batchInsert(programSourceListCopy);
        }

        //新节目点位
        if (CollectionUtils.isNotEmpty(programDeviceList)) {
            List<ProgramDevice> programDeviceListCopy = programDeviceList.stream().map(programDevice -> {
                ProgramDevice programDeviceCopy = new ProgramDevice();
                programDeviceCopy.setDeviceCode(programDevice.getDeviceCode());
                programDeviceCopy.setProgramVersion(newProgramVersion);
                programDeviceCopy.setIsDelete(Boolean.FALSE);
                programDeviceCopy.setAddTime(currentTimeMillis);
                return programDeviceCopy;
            }).collect(Collectors.toList());
            programDeviceService.batchInsert(programDeviceListCopy);
        }
        return OperationResult.newSuccessTipsOperationResult("复制节目成功，新节目ID：" + copyProgram.getProgramId());
    }

    /**
     * 根据终端获取增播日志列表
     *
     * @param deviceCode 终端序列号
     * @return OperationResult
     */
    @Override
    public OperationResult getAddedSourcesByDeviceCode(String deviceCode) {
        if (StringUtils.isBlank(deviceCode)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.DEVICE_NOT_NULL);
        }
        //增播记录
        List<ProgramPlayListVO> addedProgramSourceList = programAddedLogService.list(deviceCode);
        return OperationResult.newSuccessOperationResult(addedProgramSourceList);
    }

    /**
     * 审核通过节目
     *
     * @param programVersion 设备序列号
     * @param optType        操作类型（pass：通过， reject:驳回）
     * @param rejectReason   驳回原因
     * @return OperationResult
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult auth(String programVersion, String optType, String rejectReason) {
        if (StringUtils.isBlank(optType)) {
            return OperationResult.newValidateFailOperationResult("参数审核类型不能为空");
        }
        if (optType.equals(ProgramAuthOptTypeEnum.PASS.name())) {
            //审核通过
            return pass(programVersion);
        } else if (optType.equals(ProgramAuthOptTypeEnum.REJECT.name())) {
            //驳回
            return reject(programVersion, rejectReason);
        }
        return OperationResult.newValidateFailOperationResult("参数审核类型错误");
    }

    /**
     * 更新节目播放排序
     * @param programVersion 节目版本号
     * @param rank 播放排序
     * @return
     */
    @Override
    public OperationResult updateProgramRank(String programVersion, Long rank) {
        programMapper.updateProgramRank(programVersion, rank);
        long updateTime = System.currentTimeMillis();
        //根据节目版本号查询节目终端deviceCode集合
        List<String> deviceCodeList = deviceService.listDeviceCodeByProgramVersion(Lists.newArrayList(programVersion));
        if (CollectionUtils.isNotEmpty(deviceCodeList)) {
            //批量将过期节目信息更新到缓存中的终端
            deviceService.flushDeviceStateCache(DeviceSettingType.PROGRAM_UPDATE, deviceCodeList, updateTime);
            logger.info(">>> 更新节目播放排序，刷新缓存，节目版本号：" + programVersion);
            //添加操作日志
            operationLogService.saveOperationLog(createSuccessOperationLog(ModuleConstant.MODULE_PROGRAM,
                    ">>> 更新节目播放排序，刷新设备缓存，节目版本号：" + programVersion, updateTime));
        }
        return OperationResult.newSuccessOperationResult();
    }

    /**
     * 构建成功操作日志
     *
     * @param module     模块
     * @param content    内容
     * @param updateTime 操作时间
     * @return OperationLog
     */
    private OperationLog createSuccessOperationLog(String module, String content, Long updateTime) {
        OperationLog operationLog = new OperationLog();
        operationLog.setOperationModule(module);
        operationLog.setOperationContent(content);
        operationLog.setOperationStatus(BusinessConstant.OPERATION_LONG_OPERATION_STATUS_SUCCESS);
        operationLog.setOperationTime(updateTime);
        return operationLog;
    }

    /**
     * 获取终端当前的素材播放列表
     * 待播放的和正在播放的
     *
     * @param deviceCode 设备序列号
     * @param sourceName 素材名称
     * @return OperationResult
     */
    @Override
    public OperationResult getCurrentSourcesByDeviceCode(String deviceCode, String sourceName) {
        if (StringUtils.isBlank(deviceCode)) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.POINTER_NOT_NULL);
        }
        //查询设备分辨率
        String resolution = "";
        List<String> deviceCodes = new ArrayList<>();
        deviceCodes.add(deviceCode);
        List<String> distinctResolvingPowerByDeviceCodes = deviceService.getDistinctResolvingPowerByDeviceCodes(deviceCodes, null);
        if (CollectionUtils.isNotEmpty(distinctResolvingPowerByDeviceCodes)) {
            resolution = distinctResolvingPowerByDeviceCodes.get(0);
        }
        //获取最新节目版本号
        List<String> programVersions = programMapper.getNestProgramVersionsByDeviceCode(deviceCode, System.currentTimeMillis());
        if (CollectionUtils.isEmpty(programVersions)) {
            return OperationResult.newSuccessOperationResult(new ArrayList<ProgramPlayListVO>());
        }
        //除去已减播的素材，并且查询选定设备分辨率的素材
        List<ProgramPlayListVO> programSourceVOList = programMapper.getNewestSourcesByDeviceCode(deviceCode, programVersions, sourceName, resolution);
        //统计素材播放次数和时长
        getSourcesPlayCountInfoByDeviceCode(programSourceVOList, deviceCode, programVersions);
        return OperationResult.newSuccessOperationResult(programSourceVOList);
    }

    @Override
    public List<ProgramPlayListVO> getSimpleCurrentSourcesByDeviceCode(String deviceCode, String sourceName) {
        List<ProgramPlayListVO> result = new ArrayList<>();
        if (StringUtils.isBlank(deviceCode)) {
            return result;
        }
        //查询设备分辨率
        String resolution = "";
        List<String> deviceCodes = new ArrayList<>();
        deviceCodes.add(deviceCode);
        List<String> distinctResolvingPowerByDeviceCodes = deviceService.getDistinctResolvingPowerByDeviceCodes(deviceCodes, null);
        if (CollectionUtils.isNotEmpty(distinctResolvingPowerByDeviceCodes)) {
            resolution = distinctResolvingPowerByDeviceCodes.get(0);
        }
        //获取最新节目版本号
        List<String> programVersions = programMapper.getNestProgramVersionsByDeviceCode(deviceCode, System.currentTimeMillis());
        if (CollectionUtils.isEmpty(programVersions)) {
            return result;
        }
        //除去已减播的素材，并且查询选定设备分辨率的素材
        List<ProgramPlayListVO> programSourceVOList = programMapper.getNewestSourcesByDeviceCodeNew(deviceCode, programVersions, sourceName, resolution);
        return programSourceVOList;
    }

    /**
     * 获取终端当前的素材统计信息
     * 待播放的和正在播放的
     *
     * @param deviceCode 设备序列号
     * @return OperationResult
     */
    @Override
    public OperationResult getCurrentSourcesStaticInfoByDeviceCode(String deviceCode) {
        //获取最新节目版本号
        List<String> programVersions = programMapper.getNestProgramVersionsByDeviceCode(deviceCode, System.currentTimeMillis());
        if (CollectionUtils.isEmpty(programVersions)) {
            return OperationResult.newSuccessOperationResult(new ProgramDeviceCurrentSourcesStaticInfo(0, 0, 0, 0, 0));
        }
        ProgramDeviceCurrentSourcesStaticInfo countInfo = programMapper.getCurrentSourcesStaticInfoByDeviceCode(deviceCode, programVersions);
        return OperationResult.newSuccessOperationResult(countInfo);
    }

    /**
     * 根据终端统计增播和减播次数统计
     *
     * @param deviceCodeList 设备序列号
     * @return 参数为空时返回null
     */
    @Override
    public List<DeviceAddAndReducedStatisticVO> getAddedAndReducedStatisticInfoByDeviceCodes(List<String> deviceCodeList) {
        if (CollectionUtils.isEmpty(deviceCodeList)) {
            return null;
        }
        return programMapper.getAddedAndReducedStatisticInfoByDeviceCodes(deviceCodeList);
    }

    /**
     * 获取所有节目投放的终端组
     *
     * @return OperationResult
     */
    @Override
    public OperationResult getDeviceGroupSelect() {
        List<DeviceGroup> deviceGroupList = programMapper.getDeviceGroupSelect();
        return OperationResult.newSuccessOperationResult(deviceGroupList);
    }

    /**
     * 检查节目素材是否完整
     *
     * @param programVersion 节目版本号
     * @return OperationResult
     */
    @Override
    public OperationResult checkSource(String programVersion) {
        Program program = programMapper.selectByProgramVersion(programVersion);
        if (null == program) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NOT_EXISTS_NOT_NULL);
        }
        OperationResult isOk = isProgramSourceOk(program.getProgramInfo(), program.getDeviceResolution());
        return OperationResult.newSuccessOperationResult(isOk.isSucess());
    }

    /**
     * 检查节目名称
     *
     * @param programVersion 节目版本号
     * @param programName    节目名称
     * @return OperationResult
     */
    @Override
    public OperationResult checkName(String programVersion, String programName) {
        if (StringUtils.isBlank(programName)) {
            return OperationResult.newValidateFailOperationResult("节目名称不能为空");
        }
        boolean needCheck = false;
        if (StringUtils.isNotBlank(programVersion)) {
            //编辑节目，如果名称与原来名称不同则检查是否重复
            Program program = programMapper.selectByProgramVersion(programVersion);
            if (null != program && !programName.equals(program.getProgramName())) {
                needCheck = true;
            }
        } else {
            //新节目
            needCheck = true;
        }
        if (needCheck) {
            boolean programNameExists = isProgramNameExists(null, programName);
            if (programNameExists) {
                return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NAME_EXISTS);
            }
        }
        return OperationResult.newSuccessOperationResult("节目名称可用");
    }

    /**
     * 统计素材播放次数和时长
     *
     * @param programSourceVOList 素材集合
     * @param deviceCode          终端序列号
     * @param programVersions     节目版本号
     */
    private void getSourcesPlayCountInfoByDeviceCode(List<ProgramPlayListVO> programSourceVOList, String deviceCode, List<String> programVersions) {
        if (CollectionUtils.isNotEmpty(programSourceVOList)) {
            //从监播获取终端当天实时的统计数据, 返回数据json对象key:prgramVersion&@素材名称&@素材地址
            JSONObject currentCountObject = null;
            try {
                String resultJson = monitorDataFileService.getFileRealTimePlayNumDuration(deviceCode);
                logger.info("获取终端【" + deviceCode + "】当前统计信息：" + resultJson);
                if (StringUtils.isNotBlank(resultJson)) {
                    currentCountObject = JSONObject.parseObject(resultJson);
                }
            } catch (Exception e) {
                logger.error("获取终端素材播放统计失败： " + e.getMessage()
                        + "\n\t序列号：" + deviceCode
                        + "\n\t日期：" + DateUtils.longTimeStampToStringDate(System.currentTimeMillis(), DateUtils.PATTEN_YMD));
            }
            //获取终端历史播放统计
            Map<String, JSONObject> historyCountMap = getFileRealTimeDataAsMap(deviceCode, programVersions);
            logger.info("获取终端【" + deviceCode + "】历史统计信息：" + (null == historyCountMap ? "" : historyCountMap.toString()));

            for (ProgramPlayListVO programPlayListVO : programSourceVOList) {
                //拼接key: 节目版本号&@素材名称&@素材url
                String key = programPlayListVO.getProgramVersion() + ElasticsearchConstant.SEPARATOR + programPlayListVO.getFileName() + ElasticsearchConstant.SEPARATOR + programPlayListVO.getUrl();
                JSONObject currentCountInfo = null;
                JSONObject historyCountInfo = null;
                if (null != currentCountObject) {
                    currentCountInfo = currentCountObject.getJSONObject(key);
                }
                if (null != historyCountMap && !historyCountMap.isEmpty()) {
                    historyCountInfo = historyCountMap.get(key);
                }
                setPlayDurationAndNumCount(programPlayListVO, currentCountInfo, false);
                setPlayDurationAndNumCount(programPlayListVO, historyCountInfo, true);
            }
        }
    }

    /**
     * 设置播放次数和播放时长
     *
     * @param programPlayListVO 播放实体类
     * @param countObj          统计json对象
     */
    private void setPlayDurationAndNumCount(ProgramPlayListVO programPlayListVO, JSONObject countObj, Boolean isHistory) {
        if (null != countObj) {
            Long count = countObj.getLong(ElasticsearchConstant.PLAY_NUM_COUNT);
            Long duration = countObj.getLong(isHistory ? ElasticsearchConstant.PLAY_DURATION : ElasticsearchConstant.PLAY_DURATION_SUM);
            programPlayListVO.setPlayCount(programPlayListVO.getPlayCount() + (count == null ? 0L : count));
            programPlayListVO.setPlayDuration(programPlayListVO.getPlayDuration() + (duration == null ? 0L : duration));
        }
    }

    /**
     * 是否能删除节目
     * 1.不需要审核的，只要发布后不能删除，即：待播放，播放中的不能删除
     * 2.需要审核的，发布后也不能删除，即：待审批，待播放，播放中的不能删除
     *
     * @param programDetailVO 节目
     * @param needAuth        是否需要审核 true需要， false不需要
     * @return true可以删除， false不能删除
     */
    private boolean canDelete(ProgramDetailVO programDetailVO, boolean needAuth) {
        if (ProgramStatusEnum.PLAYING.getName().equals(programDetailVO.getProgramStatusName())) {
            return false;
        }
        if (ProgramStatusEnum.TO_BE_PLAY.getName().equals(programDetailVO.getProgramStatusName())) {
            return false;
        }
        if (ProgramStatusEnum.TO_BE_APPROVAL.getName().equals(programDetailVO.getProgramStatusName())) {
            return false;
        }
        return !needAuth || !ProgramStatusEnum.TO_BE_APPROVAL.getName().equals(programDetailVO.getProgramStatusName());
    }

    /**
     * 检测节目素材是否完成
     * 根据节目分辨率比较节目素材JSON是否完成
     *
     * @param programInfo      节目素材JSON信息
     * @param deviceResolution 节目所选分辨率（多个英文逗号隔开）
     * @return 返回json对象（key为分辨率，值为json内容），不为空表示素材完整， 返回null表示不完整
     */
    private String getProgramSourceJsonInfo(String programInfo, String deviceResolution) {
        if (StringUtils.isBlank(programInfo)) {
            return null;
        }
        List<String> resolutions = Arrays.asList(deviceResolution.split(SymbolConstant.COMMA_EN));
        JSONObject jsonObject = JSONObject.parseObject(programInfo);
        Set<String> keySet = jsonObject.keySet();
        if (CollectionUtils.isEmpty(keySet)) {
            return null;
        }
        JSONObject jsonInfo = null;
        for (String resolution : resolutions) {
            JSONObject info = jsonObject.getJSONObject(resolution);
            if (null == info) {
                return null;
            }
            Long jsonId = info.getLong(JsonKeyEnum.JSON_ID.getValue());
            if (null == jsonId) {
                return null;
            }
            if (null == jsonInfo) {
                jsonInfo = new JSONObject();
            }
            JsonData jsonData = jsonDataService.selectByPrimaryKey(jsonId);
            if (null != jsonData && StringUtils.isNotBlank(jsonData.getJsonContent())) {
                jsonInfo.put(resolution, JSONArray.parseArray(jsonData.getJsonContent()));
            } else {
                jsonInfo.put(resolution, new JSONArray());
            }
        }
        return jsonInfo == null ? "" : jsonInfo.toJSONString();
    }

    /**
     * 检测节目素材是否完成
     * 根据节目分辨率比较节目素材JSON是否完成
     *
     * @param programInfo      节目素材JSON信息
     * @param deviceResolution 节目所选分辨率（多个英文逗号隔开）
     * @return 返回json对象（key为分辨率，值为json内容），不为空表示素材完整， 返回null表示不完整
     */
    private OperationResult isProgramSourceOk(String programInfo, String deviceResolution) {
        OperationResult failResult = OperationResult.newValidateFailOperationResult("节目内容不完整，请补全节目内容");
        if (StringUtils.isBlank(programInfo)) {
            return failResult;
        }
        if (StringUtils.isBlank(deviceResolution)) {
            return failResult;
        }
        List<String> resolutions = Arrays.asList(deviceResolution.split(SymbolConstant.COMMA_EN));
        if (CollectionUtils.isEmpty(resolutions)) {
            return failResult;
        }
        JSONObject jsonObject = JSONObject.parseObject(programInfo);
        if (null == jsonObject) {
            return failResult;
        }
        Set<String> keySet = jsonObject.keySet();
        if (CollectionUtils.isEmpty(keySet)) {
            return failResult;
        }
        for (String resolution : resolutions) {
            JSONObject info = jsonObject.getJSONObject(resolution);
            if (null == info) {
                return failResult;
            }
            Long jsonId = info.getLong(JsonKeyEnum.JSON_ID.getValue());
            if (null == jsonId) {
                return failResult;
            }
            JsonData jsonData = jsonDataService.selectByPrimaryKey(jsonId);
            if (null == jsonData) {
                return failResult;
            }
            String jsonContent = jsonData.getJsonContent();
            if (StringUtils.isBlank(jsonContent)) {
                return failResult;
            }
            if (StringUtils.isBlank(jsonContent)) {
                return failResult;
            }
            OperationResult operationResult = checkSourceCompleted(jsonContent, resolution);
            if (!operationResult.isSucess()) {
                return operationResult;
            }
        }
        return OperationResult.newSuccessOperationResult(true);
    }

    /**
     * 构造节目设备关联对象
     *
     * @param currentTimeMills 当前时间戳（毫秒）
     * @param programVersion   节目版本号
     * @param deviceCode       设备序列号
     * @return ProgramDevice
     */
    private ProgramDevice getProgramDevice(long currentTimeMills, String programVersion, String deviceCode) {
        ProgramDevice bean = new ProgramDevice();
        bean.setAddTime(currentTimeMills);
        bean.setDeviceCode(deviceCode);
        bean.setProgramVersion(programVersion);
        return bean;
    }

    /**
     * 构造节目下载
     *
     * @param programVersion 节目版本号
     * @param deviceCode     设备序列号
     * @return ProgramDownload
     */
    private ProgramDownload getProgramDownload(String programVersion, String deviceCode) {
        ProgramDownload bean = new ProgramDownload();
        bean.setDeviceCode(deviceCode);
        bean.setProgramVersion(programVersion);
        //下载状态初始为‘未开始’
        bean.setDownloadStatus(ProgramDownloadStatusEnum.NOT_START.getValue());

        //下发状态初始为‘发送中’
        bean.setPushStatus(ProgramPushStatusEnum.PUSHING.getValue());
        //从系统中读取：允许最晚发送成功时间（终端开始调用下载接口，单位毫秒）, 允许值 = 当前时间戳 + 设置时间, 默认24小时
        long defaultTime = System.currentTimeMillis() + ProgramConstant.DEFAULT_ALLOW_SUCCESS_PUSH_LATEST_TIME * ProgramConstant.UNIT_HOUR_TO_MILLS;
        Sysconfig sysconfig = sysConfigService.getByType(SysConfigNameEnum.ALLOW_SUCCESS_PUSH_LATEST_TIME);
        bean.setAllowSuccessPushLatestTime(null == sysconfig ? defaultTime : (System.currentTimeMillis() + Long.valueOf(sysconfig.getValue()) * ProgramConstant.UNIT_HOUR_TO_MILLS));
        return bean;
    }

    /**
     * 批量新增节目终端关联关系
     *
     * @param programVersions 节目版本号
     * @param deviceCodes     终端序列号（多个英文逗号隔开）
     * @param checkPointer    是否检查
     */
    private OperationResult batchInsertProgramDeviceCode(String programVersions, String deviceCodes, Boolean checkPointer) {
        List<String> deviceCodeList = null;

        if (checkPointer) {
            if (StringUtils.isBlank(deviceCodes)) {
                return OperationResult.newValidateFailOperationResult(MsgConstant.POINTER_NOT_NULL);
            }
            deviceCodeList = Arrays.asList(deviceCodes.split(SymbolConstant.COMMA_EN));
            if (CollectionUtils.isEmpty(deviceCodeList)) {
                return OperationResult.newValidateFailOperationResult(MsgConstant.POINTER_NOT_NULL);
            }
        } else {
            //不需要检查，如果点位为空直接返回成功
            return OperationResult.newSuccessOperationResult();
        }
        if (CollectionUtils.isNotEmpty(deviceCodeList)) {

            List<String> programVersionList = Arrays.asList(programVersions.split(SymbolConstant.COMMA_CN));

            if (CollectionUtils.isNotEmpty(programVersionList)) {
                long currentTimeMills = System.currentTimeMillis();
                List<ProgramDevice> programDeviceList = new ArrayList<>();
                List<ProgramDownload> programDownloadList = new ArrayList<>();

                for (String programVersion : programVersionList) {
                    for (String deviceCode : deviceCodeList) {
                        programDeviceList.add(getProgramDevice(currentTimeMills, programVersion, deviceCode));
                        programDownloadList.add(getProgramDownload(programVersion, deviceCode));
                    }
                }

                // 清理原有数据
                programDeviceService.batchDelete(programVersionList, null);
                programDownloadService.batchDelete(programVersionList);

                //新增新的数据
                programDeviceService.batchInsert(programDeviceList);
                programDownloadService.batchInsert(programDownloadList);
            }
        }
        return OperationResult.newSuccessOperationResult();
    }

    /**
     * 解析素材JSON字符串
     *
     * @param jsonContent    素材集合JSON字符串
     * @param programVersion 节目版本号
     * @param resolution     设备分辨率
     * @return Map key(sources, duration, size)
     */
    private Map<String, Object> parseSourceAndRelateToProgram(String jsonContent, String programVersion, String resolution) {
        logger.info("parseSourceAndRelateToProgram开始解析素材...");
        JSONArray jsonArray = JSONArray.parseArray(jsonContent);
        if (CollectionUtils.isEmpty(jsonArray)) {
            return null;
        }
        Map<String, Object> contentMap = new HashMap<>(3);

        boolean isSourceMixedUse = false;
        boolean contantsImageOrVideo = false;
        boolean contantsWeb = false;

        //所有素材累加大小，单位：字节 B
        long totalSize = 0L;
        //所有素材累加时长，单位：毫秒 ms
        int totalDuration = 0;
        List<ProgramSource> programSourceList = new ArrayList<>();
        long currentTimeMillis = System.currentTimeMillis();
        Sysconfig webImg = sysConfigService.getByType(SysConfigNameEnum.DEFAULT_WEB_IMAGE);
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String fileName = jsonObject.getString("fileName");
            String fileType = jsonObject.getString("fileType");
            String url = jsonObject.getString("url");
            String md5 = jsonObject.getString("md5");
            long size = jsonObject.getLongValue("size");
            int duration = jsonObject.getIntValue("duration");
            String fileImageUrl = jsonObject.getString("fileImageUrl");

            if (StringUtils.isBlank(url) || StringUtils.isBlank(fileName) || 0 == size || 0 == duration) {
                continue;
            }
            totalSize += size;
            totalDuration += duration;

            //处理默认显示图片
            if (StringUtils.isBlank(fileImageUrl)) {
                if (FileTypeEnum.IMAGE.getValue().equals(fileType)) {
                    //图片缩略图使用图片本身
                    fileImageUrl = url;
                } else if (FileTypeEnum.WEB.getValue().equals(fileType)) {
                    // web使用系统配置的默认图
                    if (null != webImg) {
                        fileImageUrl = webImg.getValue();
                    }
                }
            }

            ProgramSource programSource = new ProgramSource(programVersion, url, md5, fileName, fileType, currentTimeMillis, fileImageUrl, duration, resolution);
            programSourceList.add(programSource);

            if (FileTypeEnum.IMAGE.getValue().equalsIgnoreCase(fileType) || FileTypeEnum.VIDEO.getValue().equalsIgnoreCase(fileType)) {
                contantsImageOrVideo = true;
            } else if (FileTypeEnum.WEB.getValue().equalsIgnoreCase(fileType)) {
                contantsWeb = true;
            }
        }
        if (contantsWeb && contantsImageOrVideo) {
            isSourceMixedUse = true;
        }
        contentMap.put("sources", programSourceList);
        contentMap.put("duration", totalDuration);
        contentMap.put("size", totalSize);
        contentMap.put("isSourceMixedUse", isSourceMixedUse);
        logger.info("parseSourceAndRelateToProgram完成解析素材...");
        return contentMap;
    }

    /**
     * 检查素材是否完整，正确
     *
     * @param jsonContent 素材集合JSON字符串
     * @return Map key(sources, duration, size)
     */
    private OperationResult checkSourceCompleted(String jsonContent, String resolution) {
        JSONArray jsonArray = JSONArray.parseArray(jsonContent);
        if (CollectionUtils.isEmpty(jsonArray)) {
            return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】无素材");
        }
        boolean contantsImageOrVideo = false;
        boolean contantsWeb = false;

        //所有素材累加大小，单位：字节 B
        long totalSize = 0L;
        //所有素材累加时长，单位：毫秒 ms
        int totalDuration = 0;
        int sourceCount = 0;
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String fileName = jsonObject.getString("fileName");
            String fileType = jsonObject.getString("fileType");
            String url = jsonObject.getString("url");
            long size = jsonObject.getLongValue("size");
            int duration = jsonObject.getIntValue("duration");
            if (StringUtils.isBlank(url) || StringUtils.isBlank(fileName) || 0 == size || 0 == duration) {
                continue;
            }
            totalSize += size;
            totalDuration += duration;
            if (FileTypeEnum.IMAGE.getValue().equalsIgnoreCase(fileType) || FileTypeEnum.VIDEO.getValue().equalsIgnoreCase(fileType)) {
                contantsImageOrVideo = true;
            } else if (FileTypeEnum.WEB.getValue().equalsIgnoreCase(fileType)) {
                contantsWeb = true;
            }
            sourceCount++;
        }
        if (contantsWeb && contantsImageOrVideo) {
            return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】素材图片视频不能和WEB同时使用");
        }
        //节目与素材关联对象结合
        if (sourceCount == 0) {
            return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】无素材");
        }
        // 素材大小
        if (totalDuration == 0) {
            return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】素材时长为0");
        }
        // 素材时长
        if (totalSize == 0) {
            return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】素材大小为0");
        }
        return OperationResult.newSuccessOperationResult();
    }

    /**
     * 发布节目（单个）
     * 根据配置若需要审核，则更改节目为‘待审核’，若不需要则更改节目为‘到待播放’
     *
     * @param program 节目
     * @return 返回操作语句
     */
    private String publishOrAuthProgram(Program program) {
        String optMsg = "审核节目成功，节目ID：" + program.getProgramId();
        String optType = "审核节目";
        long currentTimeMillis = System.currentTimeMillis();
        Sysconfig sysconfig = sysConfigService.getByType(SysConfigNameEnum.NEED_AUTH_PROGRAM);
        boolean needAuth = Boolean.parseBoolean(sysconfig.getValue());
        // 待播放
        int newStatus = ProgramStatusEnum.TO_BE_PLAY.getValue();
        if (needAuth) {
            if (ProgramOriginEnum.API.getValue().equals(program.getOrigin())) {
                logger.info("\n\tAPI节目不需要审核，因此直接下发给终端");
                optMsg = "审批节目成功，节目ID：" + program.getProgramId();
                optType = "审批节目";
                // 需要审核则为待审核
                newStatus = ProgramStatusEnum.TO_BE_PLAY.getValue();
            } else {
                optMsg = "发布节目成功，节目ID：" + program.getProgramId();
                optType = "发布节目";
                // 需要审核则为待审核
                newStatus = ProgramStatusEnum.TO_BE_APPROVAL.getValue();
            }
        } else {
            //不需要审核，直接下发到终端，刷新终端对应的缓存
            List<String> programVersionList = new ArrayList<>();
            programVersionList.add(program.getProgramVersion());
            logger.info("\n\t系统设置为不需要审核，因此直接下发给终端");
            flushCacheByProgramVersionList(programVersionList, currentTimeMillis);
            //发布时间
            program.setPushTime(currentTimeMillis);
        }
        if (ProgramOriginEnum.API.getValue().equalsIgnoreCase(program.getOrigin())) {
            //API添加节目修改人默认为添加人
            program.setUpdateUserName(program.getAddUserName());
        } else {
            program.setUpdateUserName(UserContextHolder.currentUser().getUserName());
        }
        program.setProgramStatus(newStatus);
        program.setUpdateTime(currentTimeMillis);
        program.setRejectReson(null);
        programMapper.updateByPrimaryKey(program);

        //保存操作日志
        if (ProgramOriginEnum.API.getValue().equals(program.getOrigin())) {
            programLogService.insertSuccessLog(program.getProgramVersion(), program.getAddUserName(), optType);
        } else {
            programLogService.insertSuccessLog(program.getProgramVersion(), optType);
        }
        return optMsg;
    }

    /**
     * 发布节目（批量）
     * 根据配置若需要审核，则更改节目为‘待审核’，若不需要则更改节目为‘待播放’直接下发到终端，刷新终端对应的缓存
     *
     * @param programVersionList 节目版本号集合
     * @param programIdList      节目ID集合
     * @return 返回操作语句
     */
    private String batchPublishOrAuthProgram(List<String> programVersionList, List<Long> programIdList) {
        String optMsg = "审核节目成功，节目版本号为：" + programVersionList.toString();
        String optType = "审核通过节目";
        long currentTimeMillis = System.currentTimeMillis();
        Sysconfig sysconfig = sysConfigService.getByType(SysConfigNameEnum.NEED_AUTH_PROGRAM);
        boolean needAuth = Boolean.parseBoolean(sysconfig.getValue());
        // 待播放
        int newStatus = ProgramStatusEnum.TO_BE_PLAY.getValue();
        if (needAuth) {
            optMsg = "发布节目成功，节目ID为：" + programIdList.toString();
            optType = "发布节目";
            // 需要审核则为待审核
            newStatus = ProgramStatusEnum.TO_BE_APPROVAL.getValue();
        } else {
            //不需要审核，直接下发到终端，刷新终端对应的缓存
            flushCacheByProgramVersionList(programVersionList, currentTimeMillis);
        }
        //批量修改节目状态
        List<Program> programList = getProgramForUpdate(programVersionList, newStatus, UserContextHolder.currentUser().getUserName(), currentTimeMillis, currentTimeMillis);
        programMapper.batchUpdateSelective(programList);
        //保存操作日志
        programLogService.batchInsertSuccessLog(programVersionList, optType);
        return optMsg;
    }

    /**
     * 构造节目对象（部分字段）
     *
     * @param programVersionList 节目版本号
     * @param newStatus          新状态
     * @param updateUserName     修改用户名称
     * @param updateTime         更新时间
     * @param pushTime           发布时间（需要审批时则为审批通过时间）
     * @return List
     */
    private List<Program> getProgramForUpdate(List<String> programVersionList, int newStatus, String updateUserName, Long updateTime, Long pushTime) {
        if (null == programVersionList || programVersionList.size() == 0) {
            return null;
        }
        return programVersionList.stream().map(item -> {
            Program program = new Program();
            program.setPushTime(pushTime);
            program.setUpdateTime(updateTime);
            program.setUpdateUserName(updateUserName);
            program.setProgramStatus(newStatus);
            program.setProgramVersion(item);
            program.setRejectReson(null);
            return program;
        }).collect(Collectors.toList());
    }


    /**
     * 保存节目素材
     *
     * @param program     节目
     * @param json        素材JSON信息 （key为分辨率，value为素材信息）
     * @param checkSource 是否检查素材（true是,false否）, false则为暂存
     * @return OperationResult
     */
    private OperationResult saveProgramSourceLocal(Program program, String json, Boolean checkSource) {
        logger.info("saveProgramSourceLocal开始保存素材...");
        //当前时间戳
        long currentTimeMills = System.currentTimeMillis();

        // 解析分辨率
        String[] resolutionArr = program.getDeviceResolution().split(SymbolConstant.COMMA_EN);

        //节目素材关联
        List<ProgramSource> programSourceList = new ArrayList<>();

        //转换素材
        JSONObject jsonObject = null;

        jsonObject = JSONObject.parseObject(json);
        //检查素材
        if (checkSource) {
            if (null == jsonObject || CollectionUtils.isEmpty(jsonObject.keySet())) {
                return OperationResult.newValidateFailOperationResult(MsgConstant.SOURCE_CONTENT_NOT_NULL);
            }
        }

        int totalDuration = 0;
        long totalSize = 0;
        for (String resolution : resolutionArr) {
            //素材json串
            String jsonContent = jsonObject.getString(resolution);
            //构造programInfo
            createProgramJsonInfo(program, resolution, jsonContent, currentTimeMills);
            if (StringUtils.isBlank(jsonContent)) {
                if (checkSource) {
                    return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】无素材");
                } else {
                    continue;
                }
            }

            // 解析素材 返回key(sources, duration, size)
            Map<String, Object> jsonContentMap = parseSourceAndRelateToProgram(jsonContent, program.getProgramVersion(), resolution);
            if (null == jsonContentMap || jsonContentMap.size() == 0) {
                if (checkSource) {
                    return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】无素材");
                } else {
                    continue;
                }
            }

            //判断素材是否混用
            boolean isSourceMixedUse = (boolean) jsonContentMap.get("isSourceMixedUse");
            if (isSourceMixedUse) {
                if (checkSource) {
                    return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】素材图片视频不能和WEB同时使用");
                }
            }

            //节目与素材关联对象结合
            List<ProgramSource> programSources = (List<ProgramSource>) jsonContentMap.get("sources");
            if (CollectionUtils.isEmpty(programSources)) {
                if (checkSource) {
                    return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】无素材");
                } else {
                    continue;
                }
            }

            // 素材大小
            int duration = (int) jsonContentMap.get("duration");
            if (duration == 0) {
                if (checkSource) {
                    return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】素材时长为0");
                } else {
                    continue;
                }
            }

            // 素材时长
            long size = (long) jsonContentMap.get("size");
            if (size == 0) {
                if (checkSource) {
                    return OperationResult.newValidateFailOperationResult("分辨率【" + resolution + "】素材大小为0");
                } else {
                    continue;
                }
            }
            totalSize += size;
            totalDuration += duration;
            programSourceList.addAll(programSources);
        }

        program.setProgramSize(totalSize);
        program.setProgramDuration(totalDuration);

        //批量删除原有素材
        programSourceService.batchDelete(program.getProgramVersion());
        //批量添加节目素材关联
        if (CollectionUtils.isNotEmpty(programSourceList)) {
            programSourceService.batchInsert(programSourceList);
        }

        //更新节目字段信息
        program.setUpdateTime(currentTimeMills);
        String userName = "";
        if (ProgramOriginEnum.SELF.getValue().equals(program.getOrigin())) {
            //运营后台添加
            userName = UserContextHolder.currentUser().getUserName();
        } else if (ProgramOriginEnum.API.getValue().equals(program.getOrigin())) {
            //api添加
            userName = program.getAddUserName();
        }
        program.setUpdateUserName(userName);
        logger.info("saveProgramSourceLocal完成保存素材...");
        return OperationResult.newSuccessTipsOperationResult("保存节目素材成功");
    }

    /**
     * 构建节目jsonInfo
     *
     * @param program      节目
     * @param resolution   分辨率
     * @param json         json内容
     * @param currentMills 当前时间毫秒
     */
    private void createProgramJsonInfo(Program program, String resolution, String json, Long currentMills) {
        String programInfo = program.getProgramInfo();
        JSONObject info = null;
        JSONObject resolutionInfo = null;
        Long jsonId = null;
        if (null == programInfo || StringUtils.isBlank(programInfo)) {
            info = new JSONObject();
        } else {
            info = JSON.parseObject(programInfo);
            if (StringUtils.isNotBlank(resolution)) {
                resolutionInfo = info.getJSONObject(resolution);
                if (null != resolutionInfo) {
                    jsonId = resolutionInfo.getLong(JsonKeyEnum.JSON_ID.getValue());
                }
            }
        }

        if (null == resolutionInfo) {
            resolutionInfo = new JSONObject();
        }
        //处理节目的json数据
        JsonData jsonData = null;

        //节目json
        if (null == jsonId) {
            jsonData = new JsonData();
            jsonData.setAddTime(currentMills);
            jsonData.setJsonContent(json);
            jsonDataService.insert(jsonData);
        } else {
            jsonData = jsonDataService.selectByPrimaryKey(jsonId);
            if (jsonData != null) {
                jsonData.setJsonContent(json);
                jsonData.setUpdateTime(currentMills);
                jsonDataService.updateByPrimaryKey(jsonData);
            } else {
                jsonData = new JsonData();
                jsonData.setAddTime(currentMills);
                jsonData.setJsonContent(json);
                jsonDataService.insert(jsonData);
                //删除旧的jsonData数据
                jsonDataService.deleteByPrimaryKey(jsonId);
            }
        }
        resolutionInfo.put(JsonKeyEnum.JSON_ID.getValue(), jsonData.getJsonId());
        info.put(resolution, resolutionInfo);
        program.setProgramInfo(info.toJSONString());
    }

    /**
     * 刷新节目所有终端缓存
     *
     * @param programVersionList 节目版本号
     * @param serialNo           批次号，唯一且有意义。（如：时间戳）
     */

    private void flushCacheByProgramVersionList(List<String> programVersionList, Long serialNo) {
        List<String> codeList = deviceService.listDeviceCodeByProgramVersion(programVersionList);
        deviceService.flushDeviceStateCache(DeviceSettingType.PROGRAM_UPDATE, codeList, serialNo);
    }

    /**
     * 刷新多个终端缓存
     *
     * @param deviceCodeList 终端序列号
     * @param serialNo       批次号，唯一且有意义。（如：时间戳）
     */
    private void flushCacheByDeviceCodeList(List<String> deviceCodeList, Long serialNo) {
        deviceService.flushDeviceStateCache(DeviceSettingType.PROGRAM_UPDATE, deviceCodeList, serialNo);
    }

    /**
     * 判断节目名称是否存在
     *
     * @param programVersion 节目版本号
     * @param programName    节目名称
     * @return 返回true表示已存在， false不存在
     */
    private boolean isProgramNameExists(String programVersion, String programName) {
        return programMapper.countByName(programVersion, programName) > 0;
    }

    /**
     * 保存节目基本信息
     *
     * @param programName      节目名称
     * @param deviceType       终端类型
     * @param deviceResolution 分辨率
     * @param putinLevel       投放级别
     * @param putinStartTime   投放开始时间
     * @param putinEndTime     投放结束时间
     * @param putinLimit       投放限制
     * @return OperationResult
     */
    private OperationResult saveProgramBaseInfo(String programVersion, String programName, String deviceType,
                                                String deviceResolution, Integer putinLevel, String putinStartTime,
                                                String putinEndTime, String putinLimit) {
        //检查节目基本信息
        OperationResult checkParamResult = checkProgramBasicInfoParams(Boolean.TRUE, deviceResolution, programName, putinLevel, putinLimit, putinStartTime, putinEndTime);
        if (!checkParamResult.isSucess()) {
            return checkParamResult;
        }

        Program program = null;
        //是否需要验证节目名称唯一性
        boolean needCheckProgramNameUnique = false;

        //是否新增
        boolean isNewProgram = false;
        if (StringUtils.isBlank(programVersion)) {
            isNewProgram = true;
            programVersion = UUIDUtils.getUUIDString();
            program = new Program();
            program.setProgramVersion(programVersion);
            needCheckProgramNameUnique = true;
        } else {
            program = programMapper.selectByProgramVersion(programVersion);
            if (null == program) {
                return OperationResult.newValidateFailOperationResult("节目版本号错误");
            }
            if (!programName.equals(program.getProgramName())) {
                //名称不同需要验证唯一
                needCheckProgramNameUnique = true;
            }
        }

        //检测节目名称唯一性
        if (needCheckProgramNameUnique) {
            boolean isExists = isProgramNameExists(programVersion, programName);
            if (isExists) {
                return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NAME_EXISTS);
            }
        }

        //当前时间戳
        long currentTimeMills = System.currentTimeMillis();

        //处理投放时间
        long startTime = 0;
        long endTime = 0;
        try {
            startTime = DateUtils.stringTimeToLongTimeStamp(putinStartTime, DateUtils.PATTEN_YMD_HMS);
            endTime = DateUtils.stringTimeToLongTimeStamp(putinEndTime, DateUtils.PATTEN_YMD_HMS);
        } catch (ParseException e) {
            return OperationResult.newValidateFailOperationResult("投放时间格式错误");
        }
        if (startTime <= currentTimeMills) {
            return OperationResult.newValidateFailOperationResult("投放开始时间必须大于当前时间");
        }
        if (endTime <= currentTimeMills) {
            return OperationResult.newValidateFailOperationResult("投放结束时间必须大于当前时间");
        }
        if (startTime >= endTime) {
            return OperationResult.newValidateFailOperationResult("投放开始时间必须小于结束时间");
        }

        //将设备类型排序处理
        String deviceTypeNew = sort(deviceType);
        //分辨率排序处理
        String deviceResolutionNew = sort(deviceResolution);

        program.setPutinStartTime(startTime);
        program.setPutinEndTime(endTime);
        program.setPutinLevel(putinLevel);
        program.setPutinLimit(putinLimit);
        program.setProgramName(programName);
        program.setDeviceResolution(deviceResolutionNew);
        program.setDeviceType(deviceTypeNew);

        String optMsg = "新增节目成功";
        if (isNewProgram) {
            //新增节目
            program.setAddTime(currentTimeMills);
            program.setAddUserName(UserContextHolder.currentUser().getUserName());
            program.setProgramStatus(ProgramStatusEnum.TO_BE_PUBLISH.getValue());
            program.setOrigin(ProgramOriginEnum.SELF.getValue());
            program.setProgramType(ProgramTypeEnum.NORMAL.getValue());
            program.setIsDelete(Boolean.FALSE);
        } else {
            //更新节目
            program.setUpdateTime(currentTimeMills);
            program.setUpdateUserName(UserContextHolder.currentUser().getUserName());
        }
        return new OperationResult<>(ClientConstant.CODE_SUCCESS, optMsg, program);
    }

    /**
     * 将字符中重新排序后重组
     *
     * @param value （多个英文逗号隔开）
     * @return 重组后的字符串
     */
    private String sort(String value) {
        if (StringUtils.isBlank(value)) {
            return value;
        }
        List<String> deviceResolutionList = Arrays.asList(value.split(SymbolConstant.COMMA_EN));
        Collections.sort(deviceResolutionList);
        return Joiner.on(SymbolConstant.COMMA_EN).join(deviceResolutionList);
    }

    /**
     * 判断节目是否可发布
     *
     * @param programVersion 节目版本号
     * @return
     */
    private OperationResult ifProgramCanPublish(String programVersion) {
        // 检查节目各分辨率素材是否完整
        Program program = programMapper.selectByProgramVersion(programVersion);
        if (null == program) {
            return OperationResult.newValidateFailOperationResult(MsgConstant.PROGRAM_NOT_EXISTS_NOT_NULL);
        }

        if (!ProgramStatusEnum.canPush(program.getProgramStatus())) {
            return OperationResult.newValidateFailOperationResult("节目已发布");
        }
        return OperationResult.newSuccessOperationResult();
    }

    /**
     * 检查节目基本信息参数
     *
     * @param needCheckResolution 是否需要检查分辨率
     * @param deviceResolution    分辨率
     * @param programName         节目名称
     * @param putinLevel          节目级别
     * @param putinLimit          节目限制
     * @param putinStartTime      投放开始时间
     * @param putinEndTime        投放结束时间
     * @return
     */
    private OperationResult checkProgramBasicInfoParams(Boolean needCheckResolution, String deviceResolution,
                                                        String programName, Integer putinLevel, String putinLimit,
                                                        String putinStartTime, String putinEndTime) {
        if (needCheckResolution && StringUtils.isBlank(deviceResolution)) {
            return OperationResult.newValidateFailOperationResult("终端分辨率不能为空");
        }
        if (StringUtils.isBlank(programName)) {
            return OperationResult.newValidateFailOperationResult("节目名称不能为空");
        }
        if (programName.length() > NumberConstant.PROGRAM_NAME_MAX_LENGTH) {
            return OperationResult.newValidateFailOperationResult("节目名称不能超过" + NumberConstant.PROGRAM_NAME_MAX_LENGTH + "字");
        }
        if (null == putinLevel) {
            return OperationResult.newValidateFailOperationResult("投放级别不能为空");
        }
        if (StringUtils.isBlank(putinLimit)) {
            return OperationResult.newValidateFailOperationResult("投放限制不能为空");
        }
        if (null == putinStartTime) {
            return OperationResult.newValidateFailOperationResult("投放开始时间不能为空");
        }
        if (null == putinEndTime) {
            return OperationResult.newValidateFailOperationResult("投放结束时间不能为空");
        }
        return OperationResult.newSuccessOperationResult();
    }

    private Map<String, JSONObject> getFileRealTimeDataAsMap(String deviceCode, List<String> programVersions) {
        List<FileRealTimeDataVO> historyCountList = reportFormsService.getFileRealTimeData(deviceCode, programVersions);
        if (CollectionUtils.isEmpty(historyCountList)) {
            return null;
        }
        Map<String, JSONObject> map = new HashMap<>(historyCountList.size());
        for (FileRealTimeDataVO fileRealTimeDataVO : historyCountList) {
            if (!map.containsKey(fileRealTimeDataVO.getKey())) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.put(ElasticsearchConstant.PLAY_DURATION, fileRealTimeDataVO.getPlayDuration());
                jsonObject.put(ElasticsearchConstant.PLAY_NUM_COUNT, fileRealTimeDataVO.getPlayNum());
                map.put(fileRealTimeDataVO.getKey(), jsonObject);
            }
        }
        return map;
    }
//
//    /**
//     * 创建节目压缩包
//     *
//     * @param program 节目
//     */
//    private void createZip(Program program) {
//        //压缩节目包到华为云，并返回存储地址
//        taskExecutor.execute(() -> {
//            List<String> programZipPath = null;
//            try {
//                programZipPath = createProgramZip(program);
//                if (CollectionUtils.isNotEmpty(programZipPath)) {
//                    List<String> programVersionList = new ArrayList<>();
//                    programVersionList.add(program.getProgramVersion());
//                }
//            } catch (IOException e) {
//                logger.error("审批节目时压缩zip包失败，节目ID：" + program.getProgramId() + ", 失败原因 ：" + e.getMessage(), e);
//            }
//        });
//    }
//
//    /**
//     * 压缩节目zip包
//     *
//     * @param program 节目
//     * @return zip包存储路径
//     * @throws IOException
//     */
//    private List<String> createProgramZip(Program program) throws IOException {
//        List<String> zipUrlList = new ArrayList<>();
//        //查询节目的分辨率(可能多个)
//        String programInfo = program.getProgramInfo();
//        JSONObject info = JSON.parseObject(programInfo);
//        Set<String> resolutions = info.keySet();
//        if (CollectionUtils.isNotEmpty(resolutions)) {
//            //创建临时文件用以创建节目zip
//            File zip = ResourceUtils.getFile(ProgramZipConstant.ZIP_FOLDER);
//            String basePath = zip.getAbsolutePath();
//            String templatePath = basePath + ProgramZipConstant.SLASH + ProgramZipConstant.DEVICE_PLAY_TEMPLATE_HTML;
//            String newHtmlFilePath = basePath + ProgramZipConstant.SLASH + ProgramZipConstant.DEVICE_PLAY_INDEX_HTML;
//            String infoJsonFile = basePath + ProgramZipConstant.SLASH + ProgramZipConstant.PROGRAM_INFO_FILE_NAME;
//
//            //创建节目信息json文件
//            createInfoJsonContent(infoJsonFile, program);
//
//            //创建临时文件
//            String activeProfile = springContextUtil.getActiveProfile();
//            for (String resolution : resolutions) {
//                JSONObject jsonObject = info.getJSONObject(resolution);
//                Long jsonId = jsonObject.getLong(JsonKeyEnum.JSON_ID.getValue());
//                //创建html需要的格式的数据
//                String programJson = createJsonForZip(jsonId, program);
//                if (StringUtils.isBlank(programJson)) {
//                    continue;
//                }
//                File newHtmlFile = HtmlUtils.createNewHtmlByTemplate(templatePath, newHtmlFilePath, programJson, activeProfile, customProperties.getProgramZipTemplateHtmlUrlDomain());
//                if (null != newHtmlFile) {
//                    //临时文件夹存储zip包
//                    File zipTempFolder = new File(ProgramZipConstant.ZIP_TEMP_FOLDER);
//                    zipTempFolder.mkdir();
//
//                    // 打压缩包,例： ziptemp/10_1920x1080.zip
//                    String zipTargetPath = zipTempFolder.getAbsolutePath() + ProgramZipConstant.SLASH + program.getProgramId() + SymbolConstant.UNDERLINE + resolution + ProgramZipConstant.ZIP_SUFFIX;
//                    ZipCompressUtils zipCompressUtils = new ZipCompressUtils(zipTargetPath);
//                    zipCompressUtils.compress(basePath);
//                    logger.info("压缩成zip包成功：" + zipTargetPath);
//
//                    //将压缩包上传到华为云
//                    File zipFile = new File(zipTargetPath);
//                    String huaWeiFilePath = HuaWeiCloudUtils.uploadFile2HuaweiCloud(zipFile, ProgramZipConstant.HUA_WEI_ZIP_FOLDER);
//                    if (StringUtils.isNotBlank(huaWeiFilePath)) {
//                        FileUtils.delAllFile(basePath + ProgramZipConstant.SLASH + ProgramZipConstant.SOURCE_FOLDER);
//                        FileUtils.delAllFile(zipTempFolder.getAbsolutePath());
//                        FileUtils.delFile(infoJsonFile);
//                        newHtmlFile.delete();
//                    }
//                    logger.info("压缩包上传到华为云成功：" + huaWeiFilePath);
//                    zipUrlList.add(huaWeiFilePath);
//                    jsonObject.put(JsonKeyEnum.ZIP_URL.getValue(), huaWeiFilePath);
//                    info.put(resolution, jsonObject);
//                }
//            }
//        }
//        program.setUpdateTime(System.currentTimeMillis());
//        program.setProgramInfo(info.toJSONString());
//        program.setUpdateUserName(UserContextHolder.currentUser().getUserName());
//        programMapper.updateByPrimaryKey(program);
//        return zipUrlList;
//    }
//
//    /**
//     * 创建节目说明信息json文件
//     *
//     * @param filePath json文件存储路径
//     * @param program  节目
//     * @throws IOException
//     */
//    private void createInfoJsonContent(String filePath, Program program) throws IOException {
//        JSONObject jsonObject = new JSONObject();
//        jsonObject.put(JsonKeyEnum.PUTIN_END_TIME.getValue(), program.getPutinEndTime());
//        jsonObject.put(JsonKeyEnum.PUTIN_START_TIME.getValue(), program.getPutinStartTime());
//        jsonObject.put(JsonKeyEnum.PROGRAM_VERSION.getValue(), program.getProgramVersion());
//        jsonObject.put(JsonKeyEnum.PUTIN_LEVEL.getValue(), program.getPutinLevel());
//        jsonObject.put(JsonKeyEnum.PROGRAM_NAME.getValue(), program.getProgramName());
//        jsonObject.put(JsonKeyEnum.PROGRAM_DURATION.getValue(), program.getProgramDuration());
//        jsonObject.put(JsonKeyEnum.PROGRAM_SIZE.getValue(), program.getProgramSize());
//        java.io.File f = new File(filePath);
//        BufferedWriter o = new BufferedWriter(new FileWriter(f));
//        o.write(jsonObject.toJSONString());
//        o.close();
//    }
//
//    /**
//     * 根据md5,url组装html页面需要的资源文件名称
//     *
//     * @param md5 文件md5
//     * @param url 资源存储在华为云的url
//     * @return 新的文件名称
//     */
//    private String createSourceNameByUrl(String md5, String url) {
//        String name = "";
//        if (StringUtils.isNotBlank(md5) && StringUtils.isNotBlank(url)) {
//            //获取文件名称
//            int index = url.lastIndexOf(SymbolConstant.SLASH);
//            String sourceName = url.substring(index + 1);
//            if (StringUtils.isNotBlank(sourceName)) {
//                //构造新名称：md5 + "." + 后缀名
//                name = md5 + sourceName.substring(sourceName.indexOf("."));
//            }
//        }
//        return name;
//    }
//
//    /**
//     * 创建html中资源的url
//     *
//     * @param sourceUrlMap        资源存储在华为云的路径map
//     * @param md5                 文件md5
//     * @param url                 资源存储在华为云的url
//     * @param sourceBaseUrlInHtml html中资源的基础url
//     * @return html中资源的url
//     */
//    private String createSourceUrlInHtml(Map<String, String> sourceUrlMap, String md5, String url, String sourceBaseUrlInHtml) {
//        String sourceNameByUrl = createSourceNameByUrl(md5, url);
//        if (!sourceUrlMap.containsKey(sourceNameByUrl)) {
//            sourceUrlMap.put(sourceNameByUrl, url);
//        }
//        return sourceBaseUrlInHtml + sourceNameByUrl;
//    }
//
//
//    /**
//     * 组装终端html模板节目json
//     *
//     * @param jsonId  jsonId
//     * @param program 节目
//     * @return json内容字符串
//     */
//    private String createJsonForZip(Long jsonId, Program program) throws IOException {
//        String json = createJson(jsonId, program);
//        List<ProgramSource> source = programSourceService.listByProgramVersion(program.getProgramVersion());
//        if (CollectionUtils.isNotEmpty(source)) {
//            //创建临时zip文件包
//            File zip = ResourceUtils.getFile(ProgramZipConstant.ZIP_FOLDER);
//            //zip压缩包基础地址
//            String sourceBasePath = zip.getAbsolutePath() + ProgramZipConstant.SLASH + ProgramZipConstant.SOURCE_FOLDER;
//            //华为云存储基础地址
//            String huaWeiBaseUrl = ProgramZipConstant.PROTOCOL + ProgramZipConstant.HUAWEI_OSS_BUCKET_NAME
//                    + "." + ProgramZipConstant.HUAWEI_OSS_ENDPOINT + ProgramZipConstant.SLASH;
//
//            String sourceBaseUrl = ProgramZipConstant.HTML_JSON_URL_PREFIX;
//            // key为文件名（md5值+'.'+文件后缀）, value为华为云存储全路径
//            Map<String, String> sourceUrlMap = new HashMap<>(source.size());
//            for (ProgramSource aSource : source) {
//                //创建资源在html中的引用路径： ./source/35rr3432423423ewfw.png
//                String sourceUrlInHtml = createSourceUrlInHtml(sourceUrlMap, aSource.getMd5(), aSource.getUrl(), sourceBaseUrl);
//                //替换json中资源路径
//                String url = aSource.getUrl();
//                //处理素材url中的特殊字符
//                url = parseUrl(url);
//                json = json.replaceAll(url, sourceUrlInHtml);
//                //根据MD5查询视频的最后一帧
//                String md5 = aSource.getMd5();
//                //json中包含该视频最后一帧，替换掉
//                String thumbnailUrl = aSource.getThumbnailUrl();
//                if (StringUtils.isNotBlank(thumbnailUrl) && json.contains(thumbnailUrl)) {
//                    String grabberImageUrlInHtml = createSourceUrlInHtml(sourceUrlMap, "last_frame_image_" + md5, thumbnailUrl, sourceBaseUrl);
//                    json = json.replaceAll(thumbnailUrl, grabberImageUrlInHtml);
//                }
//
//            }
//            //下载素材到source目录下
//            for (Map.Entry<String, String> entry : sourceUrlMap.entrySet()) {
//                String url = entry.getValue();
//                String uri = url.substring(url.indexOf(huaWeiBaseUrl) + huaWeiBaseUrl.length());
//                String huaWeiUrlNew = huaWeiBaseUrl + URLEncoder.encode(uri, ProgramZipConstant.CHARSET);
//                FileUtils.downloadFileFromURL(huaWeiUrlNew, (sourceBasePath + ProgramZipConstant.SLASH + entry.getKey()));
//                logger.info("下载节目资源成功：" + url);
//            }
//        }
//        return json;
//    }
//
//    /**
//     * 创建json字符串
//     *
//     * @param jsonId  JSON主键
//     * @param program 节目
//     * @return json内容字符串
//     */
//    private String createJson(Long jsonId, Program program) {
//        JSONObject jsonObject = getJsonContentByJsonId(jsonId);
//        jsonObject.put(JsonKeyEnum.PROGRAM_VERSION.getValue(), program.getProgramVersion());
//        jsonObject.put(JsonKeyEnum.PUTIN_START_TIME.getValue(), program.getPutinStartTime());
//        jsonObject.put(JsonKeyEnum.PUTIN_END_TIME.getValue(), program.getPutinEndTime());
//        jsonObject.put(JsonKeyEnum.PROGRAM_DURATION.getValue(), program.getProgramDuration());
//        return jsonObject.toJSONString();
//    }
//
//    /**
//     * 根据jsonId查询json内容（jsonId为空时创建一个新的JSONObject）
//     *
//     * @param jsonId json数据主键
//     * @return JSONObject
//     */
//    private JSONObject getJsonContentByJsonId(Long jsonId) {
//        JSONObject jsonObject = null;
//        if (null != jsonId) {
//            JsonData jsonData = jsonDataService.selectByPrimaryKey(jsonId);
//            if (null != jsonData) {
//                String jsonContent = jsonData.getJsonContent();
//                jsonObject = JSONObject.parseObject(jsonContent);
//            } else {
//                jsonObject = new JSONObject();
//            }
//        } else {
//            jsonObject = new JSONObject();
//        }
//        return jsonObject;
//    }
//
//    /**
//     * 处理素材url中的特殊字符
//     *
//     * @param url 素材地址
//     * @return 处理后的素材地址
//     */
//    private String parseUrl(String url) {
//        if (StringUtils.isBlank(url)) {
//            return url;
//        }
//        if (url.contains(SymbolConstant.POINT)) {
//            url = url.replaceAll("\\.", "\\\\.");
//        }
//        if (url.contains(SymbolConstant.LEFT_PARENTHESIS_EN)) {
//            url = url.replaceAll("\\(", "\\\\(");
//        }
//        if (url.contains(SymbolConstant.RIGHT_PARENTHESIS_EN)) {
//            url = url.replaceAll("\\)", "\\\\)");
//        }
//        return url;
//    }
}
